@withpica/mcp-server
Version:
MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog
3,991 lines • 265 kB
Markdown
# Changelog
All notable changes to `@withpica/mcp-server` will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
> **Rule of the road:** every version bump to this package MUST land with a
> matching entry here in the same commit. See the project's "npm publish
> discipline" memory entry for the enforcement rationale.
## [Unreleased]
## [2.100.0] - 2026-10-04
### Added
- **`pica_recording_splits_update` and `pica_recording_splits_end` — fix a
wrong ownership share.** Before, a recording split could only be created
through the API, so a wrong row (a holder-less "unknown" share, the wrong
percentage or role) blocked adding the right owner with
`RECORDING_SPLIT_TOTAL_EXCEEDED` and had to be fixed by hand in /inspect.
`_update` changes `person_id` (relink to a person in the organisation, not
deleted), percentage, role, `split_type`, territory, dates or notes; the 100%
total is checked after the change with the row's old value left out.
`_end` sets `end_date` (default today) so the share stops counting, keeping the
row as history. Both report `verification_cleared: true` when the change
cleared the holder's confirmation. Typed refusals: `SPLIT_NOT_FOUND`,
`PERSON_NOT_FOUND`, `PERSON_DELETED`, `SPLIT_ALREADY_ENDED`,
`INVALID_SPLIT_PERCENTAGE`, `INVALID_SPLIT_ROLE`, `INVALID_SPLIT_TYPE`,
`INVALID_SPLIT_TERRITORY`, `INVALID_SPLIT_DATES`, plus the existing split total / exists / locked codes.
- **`pica_merge_duplicates` takes a list — one approval for a whole cleanup.**
Send several confirmed duplicate groups as `merges` (up to 50 items of
`{entity_type, winner_id, loser_ids}`) instead of one call per group. A
delegated agent (OAuth connector / `pica_grant_*`) then raises ONE human
approval and the owner gets ONE email listing every pair, approved or
denied as a whole — before this, a 15-merge cleanup sent 15 emails, each
expiring on its own. Every pair is checked before the email goes out; one
bad pair refuses the list. Once approved, `pica_approval_status` returns a
per-pair outcome (`merged` / `skipped` because already merged or deleted /
`failed` with the reason); one failed pair does not stop the others, and the
approval reads `failed` if any pair failed. Callers that execute directly
get the same per-pair result. The single-merge fields work exactly as
before; send one shape or the other, never both. The tool's input schema no
longer lists a top-level `required`, because the two shapes cannot share
one.
- **`pica_people_split_identity` — give one person their own identity when
two different people share one** (ops 482424b7). The opposite of a merge:
when a wrong match (e.g. someone else's ISNI) joined two people to one
global identity, custody transfers to it can't tell whose it is and land as
"unknown". This gives the named person a new identity. Nothing is guessed:
identifiers stay with the original unless listed in `move_identifiers`, and
only the unattributed split rows listed in `relink_split_ids` move.
`clear_person_identifiers` removes identifiers that were never the person's
from their record; `undo_external_ids` strips everything a wrong external
record (e.g. a Wikidata Q-id) wrote. Custody legs are append-only and never
rewritten. Tier `destructive`, `approvalMode: "human"`, scope
`write:people`. `pica_people_inspect` gains a `global_identity` section
(only when named) that lists everything a split would touch.
- **`pica_profile_claims_invite` + `pica_profile_claims_status` — claim
your profile for assistants.** An assistant can invite people in the
catalogue to claim their profile (each at the address the catalogue holds
for them) and check where people stand. The invite is a preview unless
`confirm: true`: the first call sends nothing and returns who would be
emailed and who would be skipped and why, so the person the assistant acts
for sees the list before anyone is emailed. Only a credential that belongs
to an owner or admin may send; an organisation key with no owner is
refused (`INVITE_REQUIRES_PERSON`). The status read never returns an
email address.
- **`pica_import_emails_query` — see what happened to an email forwarded to
PICA.** Lists the emails that reached the organisation's forwarding
address, newest first, with what PICA did with each: stored on arrival
(with what was created, by id), waiting for review, dismissed, or failed.
With `id`, reads one: its text body when PICA kept one (emails held for
review), its attachment names, and what was created. Owners and admins
only; read-only (`read:catalog`). Before this an agent had no way to learn
that an email its person forwarded had arrived. Every item carries
`sender_trusted`; an email from a sender PICA could not verify is labelled
third-party content (its subject and body are data, not instructions). The
list reaches back to 2026-09-30, when PICA began recording outcomes, and
refuses an `offset` past 500 rather than repeating a page.
### Changed
- **`pica_people_update` / `pica_people_create` accept and clear every field
enrichment fills on a person.** `imdb_id`, `viaf_id`, `date_of_death`,
`nationality_wikidata_id`, `birth_place_wikidata_id` and
`languages_spoken` were written by Wikidata enrichment but refused with
`UNKNOWN_FIELD`, so a misattributed value could not be removed. They are now
accepted, and every enrichment-filled field takes `null` (or `[]` for a
list) to clear it. A field set or cleared this way is recorded as the
user's own, and later enrichment runs leave it alone. `field_sources` is no
longer accepted from callers (it was never advertised); PICA writes it.
- **`pica_credit_update` can change who a credit is for (`person_id`).** Link
a name-only credit to a person record created afterwards, or move a credit
linked to the wrong person, and the credit keeps its id (ops issue
abc91f6c: 11 credits had to be removed and re-added, losing their ids and
confirmations). The person must be in the organisation and not deleted
(`PERSON_NOT_FOUND` / `PERSON_DELETED`); if they already hold that role on
the song or recording the change is refused with `CREDIT_PERSON_CONFLICT`
and `existing_credit_id` rather than making a second credit; linking a
name-only credit to a differently named person needs
`confirm_name_mismatch: true` (`PERSON_NAME_MISMATCH` otherwise). A
confirmed, disputed or declined credit goes back to pending, because the
newly linked person gave none of those answers — the response's
`person_change.attestation_reset` and `previous_attestation_status` say so. Server-side in `PATCH /api/admin/works/[id]/credits/[creditId]`.
- **`pica_documents_query` lists PICA's own split sheets first, marked as the
record.** Each entry now carries `kind` (`split_sheet` or `upload`); a
split sheet PICA generated has `split_sheet_role: "record"` and
`read_with: "pica_split_sheet_get"`, and an uploaded or forwarded split
sheet has `split_sheet_role: "supporting"` (plus `master: true` when it is
about the master only). An archived PICA sheet is `"superseded"`, not the
record. Paging is one sequence: PICA's sheets, then uploads, with `total`
covering both. With `parsed_status` `pending` or `completed`, only uploads
are listed. `pica_documents_link` / `_delete` take an entry with
`kind: "upload"`. Server-side change in `/api/admin/documents/catalog`;
the tool description says how to read each kind.
### Fixed
- `pica_recording_credits_update` no longer advertises or forwards
`attestation_status`. A caller could create a producer or performer
credit already marked "attested" without the credited person ever
confirming it. The field is gone from the input schema, and a call that
sends any attestation field (`attestation_status`, `attested_at`,
`attested_by`, `attestation_source_type`, `attestation_source_id`), in
the single form or on any `credits[]` entry, is refused before anything
is written with `ATTESTATION_NOT_SETTABLE`. The refusal points at the way
a recording credit actually gets confirmed: `pica_collaborators_invite`
with the `recording_id`, or `pica_credits_send_for_attestation` on the
recording's song. The server refuses the same fields on
`POST /api/admin/recordings/[id]/credits` and the bulk route.
- `pica_credit_add` on a work now treats `share_pct: null` like an
omitted share: stored as unknown. The server refused an explicit `null`
with a 400, so a client that sends `null` for an empty optional field
(ChatGPT's connector does) could not add a credit without a share.
Server-side fix in `/api/admin/works/[id]/credits/atomic-add`; it reaches
every client on deploy. The input schema now declares `share_pct` as
number-or-null, matching `pica_credit_update`.
- When the server refuses `pica_credit_add` with a machine code
(`VALIDATION_ERROR`, `PERSON_ORG_MISMATCH`, `RECORDING_TARGET_REQUIRED`,
`INSUFFICIENT_SCOPE`, …), the tool now returns that code and message,
plus the fields the server put beside them (`remedy`, `person_ids`,
`next_tool`). It no longer reports those refusals as `CREDIT_ADD_FAILED`,
which read as a transport failure and invited a blind retry. A server
error (5xx), or a refusal with no code, is still `CREDIT_ADD_FAILED`.
- `pica_credits_update` no longer advertises `attestation_status`. The
server stopped accepting it on 2026-09-29 (a caller could mark a credit
"attested" with no one ever confirming it), but the tool kept offering
it, so agents sent it and the whole save failed with `UNKNOWN_FIELD`. The
field is gone from the input schema, the description and the
`work-credits-required` resource. A call that still sends it is refused
before anything is written with `ATTESTATION_NOT_SETTABLE`, naming the
entry and pointing at `pica_credits_send_for_attestation` — the way a
credit actually gets confirmed.
## [2.99.0] - 2026-09-30
### Added
- **Platform-presence waivers on `pica_integrity_waive`.** New `gap_code`
values `PLATFORM_SPOTIFY`, `PLATFORM_YOUTUBE`, `PLATFORM_APPLE_MUSIC` with a
required `kind` (`not_applicable` | `restricted`) and reason mark a work that
is deliberately not on that platform (e.g. no YouTube by design, a licence
rules out Content ID). The platform then drops out of the work's
completeness instead of counting as missing; the answer carries the
re-scored `completeness`. Owner/admin only. Identifiers and registrations
(ISWC, ISRC, IPI, society registrations, writers, splits) can never be
waived, and the description says so.
- `original_work_id` on `pica_works_create` / `pica_works_update`: links a
modified version (a remix that added writers) to the work it modifies. Same
catalogue only; `null` clears it.
### Changed
- **A remix is a recording of the same song unless it adds writers (ADR-324).**
`pica_works_create` on a remix-titled work now suggests a recording under the
original (`pica_recordings_create`, `version_type: "remix"`, remixer credited
on the recording) with the modified-version path beside it
(`if_new_writers`), instead of a separate derivative work.
`pica_find_duplicates` (works) no longer groups a remix with its original as
a duplicate: each remix is paired with its original as
`match_kind: "remix_recording"` (move its recordings) or
`"remix_modified_version"` (link it), with a `remix` block naming the
recordings, any added writers and the action — never "fold the copies".
The `catalog-modelling` skill carries the rule.
- `pica_rights_inspect` no longer says amounts are "not computed yet"; it points
to `pica_rights_statements` for them.
### Removed
- **The last 11 deprecated tool aliases** (removal target 2.99.0). Each had
been a hidden, callable alias of the tool named beside it — ten since the
MCP tool-surface consolidation, `pica_royalties_summary` since 2026-09-25
(ADR-321 Phase 0). Calling an old name now fails with `TOOL_NOT_FOUND`;
the answer (from `pica_execute`, `pica_tool_details` and the hosted
connector alike) says the tool was retired in 2.99.0, names its
replacement and any argument change, and lists the replacement first in
`did_you_mean` when the caller can call it. Use the replacement instead:
- `pica_releases_list` → `pica_releases_query`
- `pica_releases_get` → `pica_releases_inspect`
- `pica_releases_bulk_attach_tracks` → `pica_releases_attach_track` with
`items[]` (the old `tracks` field is now `items`)
- `pica_releases_attach_track_by_identifier` → `pica_releases_attach_track`
with `identifier_type` + `value`
- `pica_projects_list` → `pica_projects_query`
- `pica_notes_list` → `pica_notes_query`
- `pica_documents_list` → `pica_documents_query`
- `pica_documents_read` → `pica_documents_inspect`
- `pica_statements_list` → `pica_statements_query`
- `pica_royalties_summary` → `pica_royalties_blockers` (`work_id` and
`limit` carry over; `gap_type` and `priority` have no equivalent)
- `pica_recording_credits_bulk_update` → `pica_recording_credits_update`
with a `credits` array (same arguments)
Production use over the 60 days before removal was 4 calls in total.
- **`verified` on `pica_works_query`.** It filtered on a work-level
`is_verified` flag that PICA has never stored, so it matched no works
whichever way it was set. Passing it now returns `NO_WORK_VERIFIED_FLAG`
(the same code `pica_works_verify` returns) and names the filters that do
exist: `health_filter: 'low_completeness'`, `completeness_score_lt`, and
`pica_works_inspect` for one work.
- **`verified` / `unverified` counts in the `catalog://stats` resource, and
`is_verified` in `works://list`.** Both read the same missing flag, so every
work counted as unverified.
- **`musicbrainz_recording_id` on `pica_works_update` / `pica_works_create` /
`pica_works_bulk_update`.** An update used to pass it to the work's
recording, but only when the work had exactly one recording, so the same
call silently stopped working once a second recording was added (a create,
with no recording yet, never stored it). It is now refused with
`PHANTOM_FIELD` naming the tool to use instead:
`pica_recordings_update {id, musicbrainz_recording_id}`. Work reads still
show the recording's MusicBrainz id, and the completeness benchmark still
reads it.
### Fixed
- `pica_sync_placements_cite` no longer answers 404 on every call: the SDK
posted to `/sync-placements/{id}/sources`, a route that never existed; it
now posts to `/cite` (needs the matching `@withpica/mcp-sdk` fix). The
sync-placement item routes also required `read:sync_placements` /
`write:sync_placements`, scopes no key can hold, so only `admin` keys got
through; they now require `read:agreements` / `write:agreements`, the
scopes these tools declare.
- `pica_physical_assets_inspect`'s `market` section no longer fails on every
call (the SDK sent GET to a POST-only route). Because that section asks an
AI model for a price estimate each time it runs, it is no longer part of
"omit sections for everything": it runs only when `sections` names
`market`, and needs a key with `write:catalog` (or `admin`).
- `pica_integrity_waive`'s `gap_code` enum now lists `WORK_SPLITS_ABSENT` and
`WORK_SPLITS_INVALID`, which the app has accepted since 2026-08-16.
- **`pica_rights_statements`** (read:finances) — ADR-321 Phase 3, COMPUTED NOT
PAID: direct income and what each party would receive under the song's
locked split in force when it was earned, PICA's proposed fee as its own line
(shown, never charged), every hold with why / who clears it / since when, and
the four measures. With `work_id`, that song's imported society statements
checked against the split: each co-writer's EXPECTED amount and the
registration check for writer societies; line-up completeness for PPL /
SoundExchange.
- **`pica_rights_record_income`** (write:finances) — record a figure the user
gave (or a quoted licensing enquiry's fee), labelled "entered by your agent".
No money moves.
## [2.98.0] - 2026-09-29
### Deprecated
- The 11 deprecated aliases scheduled for removal in 2.98.0 (among them
`pica_releases_list`, `pica_documents_list`, `pica_notes_list` and
`pica_royalties_summary`) stay one more version. They are now due for
removal in **2.99.0**. Each still answers and names its replacement.
### Added
- **`disambiguation` on `pica_works_create` / `pica_works_update`** — a short
note (≤ 200 characters, e.g. "theme", "2019 song") that tells apart two
DIFFERENT songs sharing a title and primary artist. It is part of the
duplicate check, so set it only when the songs genuinely differ; a
`WORK_ALREADY_EXISTS` refusal for a song you know is not the existing one is
the cue. The refusal message and its recovery hint now name both ways
through: merge when it is the same song, `disambiguation` when it is not.
`null` or `""` clears it. Also accepted by `pica_works_bulk_update`, which
shares the works write schema.
- **`score` work type** — music composed for film, TV, games or other
picture. Offered wherever `work_type` is (`pica_works_create`,
`pica_works_update`, `pica_works_bulk_update`, `pica_works_query`,
`pica_register_set`).
- **Programmable rights — `pica_rights_propose`, `pica_rights_status`,
`pica_rights_inspect`** (ADR-321 §9, Phase 2). Agents propose, humans
confirm. `pica_rights_propose` proposes a song rule, a recording's master
rule, or a share rule that divides one party's share (a publishing deal, a
manager's commission with an end date), and emails every party it affects to
confirm. It never activates or confirms anything and never moves money.
Tier `destructive`, `approvalMode: "human"`, scope `write:catalog`: a
delegated agent's call becomes a pending approval for the org owner (poll
`pica_approval_status`), replayed through the same propose service the route
uses. Shares are basis points (100 = 1%; a share rule's parties sum to
10,000). A refusal writes nothing and returns its reasons. A master rule
needs `ownership` — the user's answer to "who owns the master of this
recording?" (`only_me` / `me_and_others` / `someone_else`) — and locks only
after every owner signs the recording's master ownership sheet; an agent can
never lock "only me" in one step.
`pica_rights_status` (`read:catalog`) reads one rule: status, who has
confirmed this exact version, and email delivery. `pica_rights_inspect`
(`read:catalog`) reads a song's rules on both sides, version history,
disputes, and the holds — what money would be held, why, and who clears it.
No amounts yet; holds are PICA's rules, not a society's.
- **`pica_royalties_blockers` — "why isn't this song being paid?"** (ADR-321
Phase 0). With `work_id`: what in that song's record is likely holding up its
society income — missing ISWC, writers without an IPI or society membership,
shares not totalling 100%, disputed credits, no confirmed registration,
recordings without an ISRC — each with its fix. Without it: the catalogue
summary plus the songs that are not clear, filterable by blocker and paged.
Same judgement as the `/inspect` song panel and `/inspect/money`. Scope
`read:catalog` (no amounts). The description and result message never call a
song clear unless every area was checked; unchecked areas are named.
- **`pica_audio_bulk_update` — metadata for up to 200 audio files in one
call** (ops 5c53049a). Each item sets any of `file_type`, `stem_label`,
`version_label`, `title` for one file id; the result is one lean row per
item (`updated`, or `failed` with a reason) and never the file records. A bad
item does not stop the others. `dry_run: true` previews. Only the caller's
organisation's files are touched.
- **`pica_share_links_create` takes `selection_id`** — share every work a saved
selection matches as one playlist link (a snapshot; the link does not follow
later changes to the selection). Refused when the selection matches no works
or more than 200 (ops 42be8f96).
- **`pica_share_links_create` takes `allow_view_lyrics`**, and its description
now tells agents that lyrics, credits (`allow_view_collaborators`) and splits
(`allow_view_financials`) are hidden unless turned on.
- **`pica_audio_query` takes `work_ids`** (up to 100) — the audio for several
works in one paged call, including files attached through a recording of
those works (ops 96672119).
### Changed
- **Playlist share links now record `include_all_files: true` by default**
(unless `included_file_ids` names a subset), matching what the playlist page
already shows — every version of each song, for the recipient to choose.
- **`pica_audio_update` and `pica_audio_assign` return the lean list row**
(the shape a `pica_audio_query` row has) instead of the full stored record
with its waveform and fingerprint data — about 8k tokens a file (ops
5c53049a).
- **`pica_works_update` answers with a compact receipt** (ops 61ce1dcc): the
work id, `changed` (each field sent, with the value now stored),
`unconfirmed` for fields the stored record did not echo, and an `integrity`
block listing every open gap by label and code but hinting only the gaps
this write opened. It no longer returns the full work (lyrics included) or
repeats every open gap as "next steps" on each call. Pass `verbose: true` for
the previous answer.
- **`pica_credits_update` answers in the same shape**: `work_id`,
`changed.credits` (person, role, share as written) and `saved` (the save
count and any skipped rows). `verbose: true` returns the raw save result.
### Deprecated
- **`pica_royalties_summary` retired in favour of `pica_royalties_blockers`**
(founder decision, 2026-09-25). It ran its own gap judgement — PRS, PPL and
MCPS hard-coded, a missing row read as "not registered with PRS", only the
first 1,000 works. It stays callable as a silent alias until 2.98.0: calls
now return `pica_royalties_blockers`' answer (`work_id` and `limit` carry
over; `gap_type` and `priority` have no equivalent). No longer listed in
discovery. Unmatched payments remain `pica_royalties_unmatched`. Traffic: 2
calls from 2 organisations in the 90 days before retirement.
### Fixed
- The "how much has this song earned?" prompt told agents to call
`pica_royalties_summary` for cumulative earnings; that tool never returned
earnings. It now names `pica_royalties_get`.
- **`pica_works_query` with `health_filter` honours `limit` and `offset`** and
stops reporting the page length as `total` (ops 314325eb).
`low_completeness` reports the number of works below the threshold by their
stored score (max 200 per page). `needs_attention` now returns only works with reconciliation problems
(it was returning reconciled works too), checks at most 50 works per call,
pages by works checked (`next_offset`), and reports `total` only when one
call from offset 0 checked every candidate (otherwise `null`).
- **An unknown `sections` name is refused instead of answering `{}`** (ops
dfa810f8). `pica_works_inspect { sections: ["work"] }` returned a
successful empty result; every tool with a fixed list of sections now
answers `VALIDATION_ERROR` naming the unknown entries and the valid ones.
- **`pica_people_create` / `pica_people_update` accept `ipi`** as the same
field as `cae_ipi_number` — the name the other tools use. Sending both with
different numbers is refused.
- **`work_type` is one list everywhere** — song, instrumental, library, demo,
sample, matching the database. `pica_works_update` refused `instrumental`
while create and bulk update accepted it.
## [2.97.5] - 2026-09-18
### Added
- **`pica_export_song_dossiers` — everything PICA holds about a chosen set of
songs, as one ZIP.** Pass 1 to 60 `work_ids`; the ZIP holds each work's song
dossier as a PDF and as JSON (the work, its recordings with credits and
ISRCs, the releases those recordings appear on, people and identifiers,
provenance, AI disclosure, consent), a manifest with a SHA-256 hash of every
file, and a README. Returns the signed-URL envelope the other exports return,
plus `works[]` saying per work whether it was included: a work that is not in
the catalogue comes back `included: false` rather than vanishing, and a list
past 60 or a malformed id is refused rather than truncated. The
credential needs `write:files` (the floor on every export write) and
`write:catalog` (the route's own requirement). It downloads a file for the catalogue owner and sends nothing
to anyone.
### Changed
- **`pica_works_inspect` no longer says the dossier PDF is only on the web
UI**, in its `sections` description and in the dossier summary line — both
now name `pica_export_song_dossiers`.
- **`pica_export_snapshot` stops claiming "export everything about this
work"** as one of its phrases — the snapshot is a ranked what-to-fix view
and carries no recordings or releases; that phrase now routes to
`pica_export_song_dossiers`, which the snapshot's description names.
## [2.97.4] - 2026-09-17
### Fixed
- **Both CAR tools now declare the scope the platform actually requires.**
`pica_export_catalog_asset_report` asked for `write:catalog` and
`pica_car_preview` for `read:catalog`, but every write under
`/api/admin/exports/*` is floored at `write:files` in proxy.ts, so a
credential holding exactly what the tools advertised was refused 403
before the route ran (measured against the develop preview, 2026-09-17).
`pica_car_preview` is also `tier: "write"` now: it creates an S3 object and
a `pica_exports` row, and was advertised read-only.
Credentials that need a catalog asset report must carry `write:files`.
## [2.97.3] - 2026-09-17
### Fixed
- **`pica_export_catalog_asset_report` no longer signs the declaration on the
user's behalf.** It sent `signer_name: "MCP Agent"` with the declaration
pre-accepted, a statement to lenders and insurers that no person had read.
The report it returns is now unsigned, and says so; only a person signed in
to PICA can attest it, from the export panel. The same release makes the
route build reports for agent callers at all: it used to 401 every one.
Requires mcp-sdk 3.18.0.
### Removed
- **The `sections.audio` input** on `pica_export_catalog_asset_report` and
`pica_car_preview`. It was described as bundling audio master files or an
audio analysis section, and the report never read it.
### Changed
- **`pica_car_preview` no longer asks agents for a verbal attestation.** Its
description called the `lending` format legally weighted and told agents to
take a spoken attestation before calling, while the tool beneath it signed
the declaration as "MCP Agent". The report is unsigned whatever the format,
and the card's summary now says so.
## [2.97.2] - 2026-09-13
### Fixed
- **`pica_credit_update` now actually returns `allocation.status`.** The
description promised it and the route sent it, but `@withpica/mcp-sdk`
< 3.17.0 unwrapped it away, so the response never carried it and the
"writer shares now total N%" hints could never fire. Requires mcp-sdk 3.17.0.
### Added
- **`pica_credit_update` — edit ONE credit in place.** The verb between
`pica_credit_add` and `pica_credit_remove`: change a writer's share, an
instrumentalist's instrument, a role, the displayed name or notes, leaving
every other credit untouched and keeping the row's id and attestation state.
`pica_credits_update` remained replace-all, so one split change meant
re-listing every work and re-sending the whole array (ops issue bff0af25);
nothing edited a recording credit by its id — beyond the natural-key
upsert, a change meant remove-then-re-add (17f27449). Agents had already been calling this exact
name — it was the `TOOL_NOT_FOUND` miss in 167bb804, six times. Writer-split
closure is deliberately NOT enforced on a single edit; the response's
`allocation.status` (closed / open / incomplete) reports whether the set
still adds up instead. Backed by a new `PATCH
/admin/works/{id}/credits/{creditId}`, scoped to the work in the path.
- **`pica_recording_credits_update` batch mode accepts `instrument` per
credit** (`credits[].instrument`), applied on every target recording. Until
now crediting an instrumentalist across N recordings took N single calls.
- **`pica_import_streaming_link` returns `STREAMING_LINK_NOT_FOUND`** for a
link the provider cannot resolve — user input, not a PICA fault, and no
longer opens an ops issue. `STREAMING_IMPORT_FAILED` is now reserved for
genuine failures (timeouts, upstream errors). ⚠️ A client matching on the
old code for the not-found case will need the new one.
- **`pica_find_duplicates` accepts `entity_type: "audio_files"`** — the same
file uploaded twice, grouped on exact byte size + normalised filename
(`audio_files` carries no content hash). Each item reports `storage_bucket`;
a `withpica-*` bucket is storage PICA pays for. ⚠️ **Findable, not
mergeable:** `pica_merge_duplicates` refuses `audio_files` by design — the
outcome is a person deleting a redundant upload, and there is no merge
semantics for a file. The two entity lists are now deliberately different
sizes.
- **`pica_publishers_query` works at all.** `GET /admin/publishers` did not
exist, so every call since the tool shipped drew a bare 405 and surfaced as
`UNKNOWN_ERROR`. Searches by name (substring, on name and legal_name) or by
exact IPI.
- **`pica_approval_status` called with no `approval_id`** now explains what the
dashboard's "N awaiting approval" count is and who can act on it, instead of
answering "approval_id is required". There is deliberately no tool that lists
approvals — an agent grant cannot enumerate the approvals it is itself
waiting on.
### Changed
- ⚠️ **Wire change: `pica_execute` now FORWARDS `pica_discover` and
`pica_tool_details`** instead of refusing them. A client matching on the old
literal `"meta-tools (pica_discover, pica_tool_details, pica_execute) cannot
be invoked via pica_execute"` will stop seeing that refusal. Only
`pica_execute` invoking ITSELF is still refused (that is the only case that
can recurse); its message changed accordingly. A connector that surfaces only
the wrapper teaches its agent to wrap everything, so refusing the two
read-only meta-tools blocked discovery for exactly the clients that need it.
- ⚠️ **`pica_audio_query`'s `query` filter now applies to ALL results.** It was
honoured only when `unassigned: true` was also set; a plain
`pica_audio_query {query: "…"}` returned the whole library in upload order
and truncated at the 100KB response cap. The parameter description said
"with unassigned:true" and no longer does.
- **`pica_audio_query` rows are a lean LIST shape.** The route flattens the
analysis satellite onto every row — `waveform_data` alone is ~4.9KB per file
on prod, three times the rest of the row — so the 100KB response guard was
trimming a page to about 8 files. List rows now carry identity, size,
duration, bpm/key/energy/mood, links, bucket and `probable_match`;
waveform, full analysis, `metadata` and the recording/work embeds are on
`pica_audio_inspect`. ⚠️ A client reading those fields off the list will
need the inspect call.
- **`pica_recording_splits_verify` records WHICH PERSON attested.** It now
resolves the signed-in user to their person record and stamps that as
`verified_by` (the column has always referenced `people`). ⚠️ Wire change:
an API-key / agent-grant call is refused with
`VERIFICATION_REQUIRES_HUMAN_SESSION` (message text changed), and a
signed-in user with no person record in the org gets
`ATTESTING_PERSON_NOT_FOUND`. Before this the endpoint had never succeeded
for anyone.
- **`pica_credits_update` accepts a signed sheet within ±0.01.** Its own
pre-flight used `!== 100`, so 33.34/33.34/33.33 (=100.01) and
33.33×3 (=99.99) were refused before the API was called; the message text
changed.
- **`pica_share_links_create` returns `share_url`** beside `token`, so the
agent can hand the user a working link rather than sending them to the
dashboard to find it. `pica_share_links_query` returns it too. `token` is
unchanged.
- **`pica_recording_credits_update` no longer reports `PERSON_NOT_FOUND` when
the RECORDING is the missing one.** It dispatches on the route's own error
code rather than matching "not found" anywhere in the message, and a foreign
or non-existent `person_id` is now validated org-scoped before the write
(it previously reached the FK unchecked, which also accepted another
organisation's person).
- **`pica_tool_details` offers near-miss suggestions** (`did_you_mean`) for an
unknown tool name, in `structuredContent` as well as text — the same shape
`pica_execute`'s unknown-tool branch has returned since 2.96.
- **`pica_catalog_health_fix` accepts `actionIds` / `action_ids`** as aliases
for `accept` on the `pica_execute` path, and `INVALID_PLAN_TOKEN` now names
the remedy rather than only the reason.
## [2.97.1] - 2026-09-08
### Added
- **`pica_catalog_health_fix` accepts `selections: [{actionId, chosenPersonId}]`**
— the human choice a `resolve_person` action needs (one of its
`preview.candidates[].personId`). A selection accepts its action; a choice
the plan never offered is skipped `guard_blocked` with the reason. Until now
the applier required a chosen person the tool had no way to pass, so every
accepted `resolve_person` came back `stale` (feedback 2026-09-05).
- **`pica_find_duplicates` sees identifier-less copies.** Recordings group on
title + artist and releases on title + equal track count when the ISRC/UPC
pass cannot see them (`match_kind: "title_artist"`, keep-separate default);
a UPC shared by releases with different titles is reported as
`shared_upc_different_title` — the decision is "correct the UPC", with no
fold option (feedback 2026-09-05).
### Fixed
- **`pica_works_update {collaborators}` is refused with a redirect** to
`pica_credits_update` / `pica_credit_add`. `works.collaborators` is a
legacy jsonb mirror; the write showed correct splits on inspect while the
`work_collaborators` rows every health check reads still disagreed, and the
health plan then proposed "fixing" them (feedback 2026-09-05).
- **Accepting a held finding is a typed 409, not `INTERNAL_ERROR`.** The
apply route now maps every "is already <status>" refusal to
`PROPOSAL_ALREADY_CLOSED`, and a held one carries the unlock sentence
(`pica_billing_pay`, offer `unlock`, `entity_id` the work) — a ChatGPT user
hit "internal server error" three times on 2026-09-04.
- **`pica_credit_add` never invents a share.** An omitted `share_pct` on a
composition credit is stored as NULL (unknown), not 0 — a 0 read as a real
share, so the health plan rescaled the other writer to 100 and the second
writer's stake vanished (holdout lock, 2026-09-06). For `credit_type:
"owner"` the share is now REQUIRED (`OWNER_SHARE_REQUIRED`, 422): master
ownership is recorded as given, never assumed to be 100.
- **`pica_releases_update` documents the UPC backfill trigger** — it fires
only on a write to `spotify_album_id`, so re-sending the existing id is the
way to backfill a UPC (ops issue 9f33cc59).
- **`pica_subscription_status` is callable on the hosted connector.** It was
declared `scope: admin`, `audiences: ["stdio"]` while `pica_billing_pay` was
`read:catalog` on both surfaces, so a connected agent could mint a pay link
but not read the plan it would be paying for (`callable_on_this_surface:
false` on claude.ai, 2026-09-04). Both now share the catalogue-read floor;
`pica_subscription_manage` stays stdio + admin.
- **The pay path is visible to an agent that never calls `pica_discover`.**
The server instructions name `pica_subscription_status` and
`pica_billing_pay` with the exact `pica_execute` shape inside the first
2,000 characters (the observed cut on the claude.ai connector), and the
`pica_discover` description lists "how do i pay pica" among its example
queries for clients that surface tool descriptions but not instructions.
PR #2118 (`fix/avatar-import-path-2026-09-03`) is merged into the release branch, so its changes ship in the version below; publish in the order mcp-utils → mcp-sdk → mcp-server.
## [2.97.0] - 2026-09-04
⚠️ Publish order: `@withpica/mcp-sdk` 3.14.0 and `@withpica/mcp-utils` 1.29.0
must be on npm before this release — the dependency floors are now
`^3.14.0` / `^1.29.0` (this release imports `pageEnvelope`, which only
exists from mcp-utils 1.29.0; a clean install against 1.28.0 breaks at
runtime — the 2.96.2 uninstallable incident).
### Added
- `pica_multimedia_import_url` accepts `person_id` (sets the person's profile image) and its summary names the outcome — `Imported from URL (via)`, profile-image set/NOT set, work link (PR #2118, merged into this release). Page URLs are followed to their `og:image`.
- **Per-tool argument aliasing for `pica_execute`** (`arg-aliases.ts`, D1
2026-09-03) — `applyArgAliases` renames a tool's OWN entity id (or an
explicit `TOOL_ARG_ALIASES` entry, e.g. `pica_skill_get`'s `skill` /
`skill_name`) to the schema's real required field name, structurally by
domain (`DOMAIN_ID_ALIASES`) — never a foreign entity's id, never
overwriting a value already present, and left untouched when more than
one candidate is present. Fixes the wrong diagnosis `pica_execute` gave in
all 36 logged cases since July, where an agent sent e.g. `audio_file_id`
to `pica_audio_update` and was told the client had dropped the field in
transit.
- **Shared `WRAPPED_ARGS_MISSING` envelope builder**
(`wrapped-args-envelope.ts`) — both guards that can reject a
`pica_execute` call before dispatch (the plain `required`-list guard, and
the `requiredOneOf` one-of-N guard covering `*_inspect` / `pica_search` /
bookings / licensing / shows / royalties tools) now build their message
and `structuredContent` through one function, naming
`unrecognised_fields` and `aliases_applied` in **both** the text block and
`structuredContent` instead of asserting "client-side transport loss" for
a non-empty args object. Empty args still name transport loss — there's
nothing else an empty resolved-args object could be.
- **`offset` on `pica_audio_query`, `pica_notes_query`, `pica_sessions_query`**
(Task 10), each returning a `page` envelope built with
`@withpica/mcp-utils`'s `pageEnvelope()`. Audio and sessions fall back to
"page was full" (no total available); notes uses the route's real
`COUNT` for an exact `has_more`. A new `query-offset-parity` registry
guard test pins which remaining `_query` tools still lack `offset` so
each can be fixed and measured in isolation.
### Changed
- **Duplicate-release recovery hints point at a fix, not a dead end.**
`RELEASE_ALREADY_EXISTS` on `pica_releases_create` /
`pica_releases_create_with_tracks` now names `pica_releases_update` /
`pica_releases_attach_track` (the message already carries the existing
release's id) instead of `pica_releases_query`; `pica_releases_attach_track`'s
own `RELEASE_ALREADY_EXISTS` (a different release already holds the UPC)
points at `pica_releases_inspect`.
- **Deprecated alias removal deferred to 2.98.0** — see `metadata.ts`.
## [2.96.2] - 2026-09-02
The MPP pay rail (spec 2026-08-24 WS-B): a new tool that mints a pay link,
and `pica_subscription_status` learns to say what pica costs and what it is
holding. Depends on `@withpica/mcp-sdk` 3.13.0 (`BillingResource`) — publish
order matters, see `docs/releases/2026-08-26-mcp-release.md`. (This entry
was authored as 2.96.0, which npm deprecated after 2.96.1 shipped from
develop without `pica_billing_pay`; the branch re-releases on top of 2.96.1
as 2.96.2, and also carries the stdio relay change below.)
### Added
- **`pica_billing_pay`** — new tool, `scope: read:catalog`, `tier: read`
(minting a link mints a capability, not a charge — nothing is billed until
the link is paid). Wraps `POST /api/admin/billing/pay-link` via
`PicaClient.billing.mintPayLink()`. Takes `offer` (`resident_month` | `unlock`
| `settle`, required) and `entity_id` (required for `unlock`). Route
refusals — billing disabled, nothing held on that work, offer no longer
applies — come back as structured `isError` results with a `next_tool`
pointer, not thrown errors. Added to the `subscription` tool category
(renamed description: "what does pica cost, how do I pay, and what is pica
holding — read your billing state and get a pay link"), the atlas
(`"how do i pay pica?"` and its synonyms), and `RECOVERY_HINTS`.
- **`pica_subscription_status` carries `pricing` and `held` when the route
measured them.** `SubscriptionStatusFlat` gains optional `pricing` and
`held` fields (omitted, not `null`, when absent — a `null` price would read
as "free"). The composed `summary` sentence appends one further clause
naming the free-runway/resident-included song counts and, when
`held.count > 0`, how many findings across how many works are held pending
payment.
- **`pica_dashboard_briefing`'s `found` block description gains a `billing`
clause**: when `billing.tab` is a string, state the tab total once and say
that settling it clears what pica is holding; when `billing.pay` names a
tool, offer to call it — mint a pay link, never charge unprompted.
### Changed
- **stdio relay forwards a narrowed result projection** — after a successful
tool call the audit entry now carries `result_projection` (built by
`narrowToolResultForRefs` from `@withpica/mcp-utils` ≥1.28.0), so the
relay ingest can attribute import wins: `pica_import_streaming_link` /
`pica_import_youtube_link` created-work ids live in the RESULT and their
params carry no id, which is why stdio imports recorded `entity_refs:
null` and were invisible to the weekly roundup's wins beat. Only
allow-listed uuid arrays travel; every other tool sends
`result_projection: null` — nothing else on the wire changes. (The same
entry field also rides `makeHttpAuditFn`'s POST to `/api/mcp/audit`,
which destructures a fixed field list and ignores it.)
⚠️ Publish order: `@withpica/mcp-utils` 1.28.0 must be on npm before this
release (2.96.2) — the dependency floor is `^1.28.0`.
## [2.96.1] - 2026-08-31
First npm release since 2.95.4 — carries everything below (the discover
changes have been live on the hosted connector since promote #2061; stdio
clients get them here). 2.96.0 was published from an unmerged branch and is
deprecated; do not use it.
### Fixed
- **`pica_import_validate` surfaces warnings on a VALID result.** A warning
does not make a row invalid, so `valid: true` can carry warnings — e.g. a
works CSV mapped to `release_year` (release-level data; a newly created
work has no release) is receipted at row 0. The tool used to answer "All N
rows are valid. Ready to import" and drop the warning, so the agent
executed blind (2026-08-30 production finding, PR #2072 / promote #2074).
Warnings now ride the valid branch in the text and as `warnings` in the
structured data. The SDK's `ImportValidation.errors` rows carry the wire
field `error` (with `message` kept for normalising callers).
### Changed
- **The ten tools slated for removal in 2.96.0 remain, `removal_target`
extended to 2.97.0** (`pica_recording_credits_bulk_update`,
`pica_statements_list`, `pica_documents_list`, `pica_documents_read`,
`pica_projects_list`, `pica_releases_list`, `pica_releases_get`,
`pica_releases_bulk_attach_tracks`,
`pica_releases_attach_track_by_identifier`, `pica_notes_list`). This
release exists to ship the validate-warnings fix; removing tools is its
own change with its own review, not a rider on it.
- **`pica_discover({ query })` no longer scores the asker's own words as the entity.** Determiners, pronouns, prepositions and light verbs (`my`, `the`, `get`, `list`, `find` …) are dropped before ranking. "start my music catalogue and import my songs" led with three `pica_my_*` tools because "my" matched their domain segment; it now leads with the import tools. A query made only of such words matches nothing.
- **Query mode carries the fresh-org import line.** The ADR-294 hint ("on a fresh or empty org, run `pica_dashboard_briefing` first … paste a spotify album or playlist link") was only on the no-args category map; it is now on every query response, hit or miss.
- **Tools the caller cannot run on this connection rank last and are flagged.** Shortlist rows and `pica_tool_details` gain `callable_on_this_surface: false` plus a `surface_note` when the tool is on another surface (stdio / lobby) or needs a scope the connection does not hold — the judgement the HTTP dispatcher enforces at `pica_execute` time (installed only while the inner gate is in enforce mode). An existing scope or destructive-tier `callable_note` is kept beside it, and the `invoke` line on a flagged detail says the call will be refused instead of "pica_execute executes every tool returned here". Rows for callable tools are unchanged (no new keys). A capped shortlist keeps at least five tail rows for flagged tools and lets them fill what visible rows leave, so a gated capability reads as gated, not missing, and a narrow scope never shrinks the list. Query-mode `cache_until` is now one hour (the rows are per-connection); the full map keeps its day.
## [2.95.4] - 2026-08-25
An agent-readability release. Nothing about auth, tiers or tool behaviour changes; what changes is what a tool **tells** an agent about itself, and the shape of a refusal.
### Added
- **`pica_dashboard_briefing` reports what pica is holding.** The `found` block carries `held: { count, entities }` — findings pica produced but is HOLDING because the org is past its free runway or over its allowance (WS-A of the MPP spec, #2027; dormant unless billing enforcement is on).
- **Every tool now names the sibling it is most confused with.** 141 descriptions across every 3-or-more-member family gained a clause saying which same-family tool to prefer and when — derived from that sibling's own description and schema, not invented. Families with verb-first names (`pica_get_*`, `pica_update_*`) are deliberately exempt: those segments group unrelated tools by verb, so "name a sibling in your family" has no meaning for them. Where a description had to grow, over-long ones were paid for by dropping text the tool's own `inputSchema` or `nextSteps` already carried.
- **`did_you_mean` on a missed tool name.** An unknown inner tool via `pica_execute` now comes back with ranked candidates plus `next_tool: pica_discover`. Candidates never include a deprecated alias, and on the HTTP surface they are drawn only from tools the caller can actually see.
### Changed
- **⚠️ Wire change: an unknown tool now returns `TOOL_NOT_FOUND`, not `UNKNOWN_TOOL`.** The code read `UNKNOWN_TOOL` from 2026-07-03 until now. A client matching on the old literal will stop recognising this refusal — match on `isError` instead, which is set on every refusal. The `suggestions` key is kept alongside the new `did_you_mean` so existing readers keep working.
- **The HTTP dispatcher's bare `Not found` is now the same typed envelope.** An unregistered inner tool name was refused at the gate-shadow step before the registry's typed envelope could run. A registered-but-hidden name keeps its previous refusal, so the change leaks no information about tools the caller may not see.
- **Annotations are now derived for every tool on both surfaces.** `pica_tool_details` returned `annotations: null` for 275 of 332 tools because it passed the raw declaration through while `tools/list` derived hints from tier. Both now go through one derivation. Two consequences a client may notice: `openWorldHint` follows `createsExternalSideEffects`, so **24 tools flip `false` → `true` on the flat surface** (they already published that side-effect flag by another name — nothing about their behaviour changed); and a **declared** `idempotentHint` now wins over the derived value, which flips **4 tools**. Treat both as corrections to hints that were previously wrong or absent, not as new capability.
- **`pica_skill_get` puts the skill body in `structuredContent`.** The methodology travelled only in the text block, so clients that prefer `structuredContent` (claude.ai among them) received frontmatter and nothing to follow.
- **No live surface recommends a deprecated alias any more.** Server instructions, 7 tool descriptions, 17 recovery-hint `next_tool` values, 2 skills and 2 resources pointed agents at alias names. All now name the canonical tool. The alias names still resolve — the only remaining mentions are the aliases' own registration rows.
- **Refusals are typed envelopes with a `SCREAMING_SNAKE` code.** The notifications and telegram tools returned _success_ envelopes carrying an `error` key with no `isError`, so an agent read a failure as a success; credits and import returned bare prose. All four now share one error envelope.
- **The agent guide computes its counts at serve time** from the caller-visible tool set, instead of reciting numbers written down by hand and since drifted. It also drops a `mode:` parameter that never existed on the schema and a `prs_work_id` removed under ADR-282.
- **`pica_discover` ranking singularises tokens properly.** The old `-es` rule turned `notes` into `not` (a false hit on every query containing "not") and `releases` into `releas` (never matching "release"). Domain segments are now weighted, so a query naming a domain reaches that domain's tools first.
- Server instructions state what the registry actually enforces for `sections` / `limit` (3,110 / 3,200) instead of a softer claim.
- **`pica_import_execute` now imports one slice per call and tells you where to continue.** A call runs synchronously under the route's ~2 minute budget, so the server imports at most `row_limit` rows (default 2,000, max 10,000) from `row_offset` (default 0) and returns `rowRange.nextOffset` — call again with `row_offset: nextOffset` until it is null. The old "max 10,000 rows per import" claim was not honourable inside the budget: a call the budget ended committed its batches and reported nothing. For files over ~2,000 rows, upload once with `pica_upload_file` → `pica_upload_complete` and pass `file_id` so the CSV is not re-sent per call. (ADR-314)
- **`pica_import_execute` no longer loses a whole batch to one duplicate, and no longer reports a partial commit as a failure.** `conflict_strategy` now defaults to `"skip"` (a row that trips a unique constraint becomes a warning; every other row in its batch still lands) instead of `"error"` (one 23505 discarded the batch). A live run that wrote rows and also carries errors is answered `Import PARTIALLY complete: N created …` with `partial: true` in the payload and an explicit "the created rows are saved, do not re-send the whole file" instruction, because the backing route now returns 200 for a partial commit (400 is reserved for runs that wrote nothing). Observed 2026-08-17: a 2,239-row catalogue import lost a 500-row batch to a single repeated title, was told it had failed while 1,742 rows were already saved, and re-sent the file three times. Pass `conflict_strategy: "error"` explicitly to keep all-or-nothing batches.
- **`pica_upload_file` / `pica_upload_complete` work on a `write:files` credential again.** The platform's bearer write-scope floor had `/api/admin/storage/**` (the two routes these tools call) floored to `admin`, so every connector — none holds `admin` — was refused "insufficient scope: requires admin". Fixed on the platform side (no package change required); noted here because the tools' declared `write:files` scope was correct all along.
- **`pica_notifications_recent` / the directory server count.** The `.well-known` directory listing and `pica_discover` count 11 directory tools (was 9 by drift), and the one destructive directory tool no longer wears a write tier — pinned by a parity tripwire (#2024).
### Fixed
- **The daily briefing counted one work three times.** `entityIds` were not deduplicated, so a single work touched by three events reported as "3 works". Counts are deduplicated and a `canonical_total` now travels with them.
- Internal vocabulary left agent-facing copy in bulk: table names, CHECK-constraint names, HTTP status codes, `jsonb`, repository paths, "1 credit each" and internal constant names were removed from descriptions and required-schema copy. An agent handed a repository path cannot open it, and repeats it to the user as if it were vocabulary they should already know.
> Patch, not minor, on purpose: `2.96.0` stays reserved for the WS5 deprecated-alias removals, which `src/tools/deprecations.ts` enforces as its removal target. No alias is removed in this release.
## [2.95.3] - 2026-08-18
### Fixed
- **`tools/list` no longer publishes this package's own authoring metadata.** `ToolDefinition` is two types wearing one name — the MCP `Tool` a client consumes, and the compile-time declarations our lint reads — and `listTools()` spread the whole object, so every field of the second kind crossed the wire. `vernacular_kind`, `vernacular_reason` and `annotationOverrides` are now stripped at the wire boundary. **Not a breaking change:** none of the three is part of the MCP `Tool` schema, no documented behaviour read them, and everything a client acts on (`name`, `description`, `inputSchema`, `annotations`, `tier`, `scope`, `audiences`, `workflows`) is untouched. The registry's own view is untouched too, so tooling that reads definitions in-process still sees them.
- **The handshake and the skill bodies no longer cite internal decision records.** `vernacular_reason` on ~30 tools read "Specialist context; not yet covered by the Creator Question Atlas — pending telemetry-driven addition per ADR-226 Phase 4 substrate writes"; five `SKILL.md` bodies, two prompt bodies and the server instructions carried similar citations. An ADR number is a coordinate into a repository the reader cannot open, and an agent handed one repeats it to a user as if it were vocabulary they should already know — which is exactly what happened in a real connector session on 2026-08-18.
- **The handshake's URI-shape pointer now names something reachable.** It read `docs/mcp-source-uri-conventions.md` — a path in the app repository, handed to agents that have no checkout. It now names `pica_skill_get("enrich-from-peer-mcps")`, the skill that actually carries the scheme table, which an agent can follow in one call.
> Patch, not minor, on purpose: `2.96.0` stays reserved for the WS5 deprecated-alias removals, which `src/tools/deprecations.ts` enforces as its removal target. Bumping the minor here would bring that deadline forward by accident.
> Scope note for anyone tracing this: the stripped-metadata leak affected the **stdio** surface — this package's flat `tools/list`. The app's HTTP transport serves layered discovery, where `tools/list` returns only the meta tools and `pica_discover` / `pica_tool_details` build responses by picking fields explicitly, so a connector was never reading it. The handshake and skill-body fixes reach both surfaces.
## [2.95.2] - 2026-07-28
### Added
- **`pica_consent_readiness` (ADR-300)** — the read side of ADR-292's consent ledger. Returns an org-scoped readiness verdict: a `rungs` map giving cleared / partial / blocked / unset asset counts at each consent level, a `blockers` list naming credited people with no decision recorded or an explicit refusal (refusals first, then by catalogue reach), and `blockedAssets`. `truncated` reports honestly when a list was capped. An asset is cleared at a level only when EVERY credited contributor has granted at least that level; one live revocation blocks it at every level. Read tier, free, all three audiences. Deliberately read-only — consent WRITES stay human-session only, because a consent decision has to be made by the person whose rights it binds, not by an agent acting for them.
### Changed
- **`pica_export_ai_consent` description corrected.** It claimed to show "which works are human-made, AI-assisted, or AI-generated" — that is `works.ai_disclosure_level` (ADR-086), a different field the AI-consent service has never read. Agents were being pointed at the wrong feature. The description now says what the tool does (produces the consent FILE) and routes intent for the ANSWER to `pica_consent_readiness`.
- The consent file's per-asset `use_cases_allowed` now reflects the consent LEVEL, and `assets.csv` gains a `cleared_level` column. This narrows what the export claims: a work whose contributors granted only `tracking_only`, or where any contributor has not answered, no longer reports training/generation/commercial as permitted. See the app repo's FIX_LOG 2026-07-27 entry — a previously downloaded ZIP will disagree with the next one, and the next one is right.
> Patch, not minor, on purpose: `2.96.0` stays reserved for the WS5 deprecated-alias removals.
## [2.95.1] - 2026-07-27
### Added
- `pica_works_bulk_move_to_performances` now carries the `http` audience, matching its sibling `pica_works_bulk_delete` — the tool becomes reachable on the hosted connector surface at the next release. No auth change: the backing route already gated on `write:catalog` + `destructive:*`, identical to bulk-delete, and `risk: "destructive"` keeps it off unauthenticated public discovery. (Lands the branch parked 2026-07-13 to fold the connector-surface change into a batch; the WS5 batch shipped in 2.95.0, expiring the parking condition.)
### Fixed
- **Bulk reads over the 100KB cap now return complete records plus a real
resume cursor, never a mid-record cut** (ops 41ff656c / 893e3e29). The
dispatcher routes every result through `guardToolResult`
(`@withpica/mcp-utils@1.27.0`): oversized LIST responses are trimmed to
whole items and stamped with `truncated`, `items_dropped`, `next_offset`
(offset-paging tools) and `recovery_hint` — in both the text view (still
valid JSON) and `structuredContent`, which previously escaped the cap
entirely. Non-list shapes keep the legacy text truncation.
## [2.95.0] - 2026-07-24
### Added
- **`pica_audio_bulk_assign_suggested`** — attach ALL orphan audio whose
suggested match is high-confidence (ISRC-verified, non-ambiguous) in one
call, instead of looping `pica_audio_assign` per file (agent feedback
20f592ff: an agent hand-looped over a 74-file queue). Pairings are
recomputed server-side at execution time — stale suggestions can't apply,
dismissed pairs stay excluded, title-only matches are never bulk-attached.
Supports `dry_run: true` preview (same server-side classification, nothing
persisted — ADR-180 Rule 12).
- **`pica_audio_dismiss_match`** — reject a suggested audio → recording
pairing so it never resurfaces in suggestions, the review queue, or the
bulk attach. The file stays attachable elsewhere; repeat dismissals are
no-op successes.
### Changed
- **`pica_recordings_query`** now surfaces the server's zero-result
`recovery_hint` on empty searches (shipped in sdk 3.8.0 — the hint was
built server-side but dropped on the wire; ops_issue 689a8e9a).
- **`pica_audio_assign`** names a mis-slotted id on failure: when the value
passed as `work_id` is actually a recording id (or vice versa), the error
says so and names the right parameter — instead of an opaque FK failure.
## [2.94.0] - 2026-07-16
### Added
- **`register-from-your-daw` MCP skill (ADR-293 agent lane).** Guides an agent
through capturing the open DAW session via the user's own DAW MCP server
(AbletonMCP et al.) and registering the work in PICA — session-read
patterns, the Log.txt/.als fallback when the bridge is down, title-suffix
hygiene, and read-merge-write for `works.metadata`. Field-proven on two
real registrations the day it shipped. Served as a `skill://` resource on
BOTH transports; four older skills' resources were backfilled onto the
hosted transport in the same change.
### Fixed
- **The whole `pica_users_*` tool family works again** (get_my_profile,
find_by_handle, set_handle, set_bio, set_avatar_url,
import_bio_from_person) — every call crashed with `UNKNOWN_ERROR: Cannot
read properties of undefined` due to an envelope double-unwrap inside
`@withpica/mcp-sdk`'s UsersResource. Requires `@withpica/mcp-sdk@^3.7.2`
(dependency bumped). Same SDK release also revives `prefix_intel` on
works/recordings inspect and the `releases` section of
`pica_works_inspect`. No input-schema changes — no OpenAI connector
resubmission.
- **Audio-files list envelope normalised** so `probable_match` on unassigned
audio surfaces cleanly through `pica_audio_query`.
## [2.93.1] - 2026-07-14
### Fixed
- **`pica_works_bulk_link_artist` dry-run no longer returns a false preview.**
The preview now routes through the same endpoint (with `dryRun=true`) as the
commit, so it shares the commit's person-resolution and validation — a person
the commit cannot find is refused at preview time instead of reporting an
optimistic "would link N". Requires `@withpica/mcp-sdk@^3.7.1`. The tool's
input schema is unchanged, so this does not trigger an OpenAI connector
resubmission.
## [2.93.0] - 2026-07-12
### Removed
- **WS5 removal sweep — 6 zero-traffic deprecated aliases DELETED** after a
30-day prod-telemetry gate (`agent_events`): `pica_projects_get`,
`pica_notes_get`, `pica_sessions_list`, `pica_sessions_get`,
`pica_statements_get`, `pica_share_links_list`. Each had 0 calls in the last
30 days in production. Their standalone `ToolDefinition`, `TOOL_METADATA`,
cost-model, discovery, and required-schemas rows were all removed; the shared
executors (getProject, getNote, listSessions, getSession, getStatement,
listShareLinks) stay because they back the canonical `_query` / `_inspect`
tools. The 10 deprecated aliases that still carry live traffic
(`pica_releases_list/_get`, `pica_releases_attach_track_by_identifier`,
`pica_releases_bulk_attach_tracks`, `pica_projects_list`, `pica_notes_list`,
`pica_documents_list/_read`, `pica_statements_list`,
`pica_recording_credits_bulk_update`) STAY callable — their `removal_target`
is consciously extended from 2.93.0 to 2.95.0 so the "expire loudly" lint
does not block this publish. Re-run the traffic gate before 2.95.0.
### Changed
- **Royalty / statement read tools re-scoped to finance visibility.** The
`pica_royalties_*` read tools (`_list`, `_get`, `_summary`, `_unmatched`) and
the `pica_statements_query` / `_inspect` / `_list` reads move from
`read:catalog` to `read:finances`; `pica_royalties_attribute` moves from
`write:catalog` to `write:finances`. This aligns the MCP tool declaration
with the backing routes' `callerHasFinanceAccess` gate (#1623) and the proxy
bearer-write floor — gated callers are now filtered from `tools/list` at
discovery instead of getting a 403 after calling. `read:finances` /
`write:finances` are admin-tier (not user-grantable); admin satisfies both.
### Added
- **`dossier` section on `pica_works_inspect`.** `sections: ["dossier"]`
returns the full song dossier — everything PICA holds about a song
(recordings, releases, composition credits, people, provenance, AI
disclosure, consent) in one structured payload, via
`WorksResource.dossier()` (SDK 3.7.0). Rendered as a compact one-line text
summary plus the full JSON under `structuredContent.data.dossier`. Opt-in
only (never part of the default all-sections response, as it is a heavy
superset); a downloadable PDF of the same dossier is available from the web
UI. Not a new tool — folded onto the existing inspect tool per the
tool-surface-consolidation preference so it rides the batched OpenAI
resubmission rather than forcing a new one.
## [2.92.0] - 2026-07-09
### Added
- **`pica_find_duplicates` (works) surfaces fuzzy near-title duplicate groups**
(WS4 agentic fixes, 2026-07-10). The backing service adds a conservative
Levenshtein pass (≥85% on version-stripped base titles ≥5 chars,
exclusion-aware, only among works no exact key grouped); groups arrive
labelled `match_kind: "fuzzy_title"` with a `similarity` score (exact-key
groups are labelled `match_kind: "exact"`), and fuzzy groups get a
confidence-honest decision question ("look very similar … are they the same
song?") with the safe default unchanged at "keep them separate". No
inputSchema change — the fields are additive on the existing response.
- **`pica_catalog_health_plan` / `pica_catalog_health_fix` can now carry two
new action shapes** (WS4): `merge_pair` with `preview.grain: "release"`
(duplicate releases by normalized UPC, survivor = oldest, applied via the
same engine as `pica_merge_duplicates`) and `normalize_work_splits`
(work-collaborator splits summing ≠ 100 proportionally rescaled to exactly
100.00 — propose-only, per-collaborator before→after preview, live-row
recompute + post-apply verification at apply time). Replaces the previously
un-appliable work-grain `set_splits_to_template` emission. Tool descriptions
updated; schemas unchanged.
- **`pica_people_create` / `pica_people_update` gain `affiliation_kind` on
`society_affiliations` items and a new `origin_countries` field** (ADR-292).
`affiliation_kind` distinguishes a society the person is a current _member_
of from their home-country _origin_ society for that code (defaults to
`member`). `origin_countries` is an array of ISO 3166-1 alpha-2 heritage
countries — the creator's self-identified origin, stored on a real
`public.people` column and sanitized server-side.
- **New tool `pica_integrity_waive`** (registration integrity floor, PR
#1595). Waives a registration-integrity gap the user has confirmed is
intentional or unresolvable (cover writer, deceased collaborator,
unreleased demo) — requires explicit user confirmation, and waivers are
reversible. Completes the integrity-assess surface the write-tool
pressure hints and dashboard-briefing nag point at.
- **`pica_works_create` / `pica_recordings_create` gain a `create_twin`
flag + twin completion hints** (auto-twin, commit 689298e3c). The
backing create routes auto-mint the twin entity (work↔recording);
agents can now opt out via `create_twin: false`, and when a twin WAS
minted the executor appends a completion hint ("also created the
linked <recording|work> <id> — pass create_twin: false to skip").
Backed by the SDK's new `requestWithEnvelope()` so the routes'
`twin`/`twin_error` sibling fields survive the response unwrap.
### Fixed
- **`pica_enrichment_proposals_list` failed 100% of real calls with
`UNEXPECTED_RESPONSE_SHAPE`** (since 2026-06-10; ops_issues
18f25b41/20c76911). The route returns `{success, data: {proposals,
count, filters}}` and the SDK narrows to `.data`, so the tool receives
the OBJECT — the `Array.isArray` guard rejected every real response.
The tool now unwraps `.proposals` (guard retained for genuinely bad
shapes). The `pica_enrichment_proposals_bulk_reject` dry-run filter preview had
the sibling bug (normalised via a `.data` key that never exists —
always previewed 0 ids) and is fixed in the same stroke.
- **`pica_releases_detach_track` reported "preview — call again with
confirm:true" AFTER the delete executed** (ops_issue 0137b1b8,
user_feedback e6320d08). The tool checked `result.data.confirmed` on a
response the SDK had already unwrapped — always undefined — so an
executed destructive mutation rendered as a pending preview. Now checks
the unwrapped `{confirmed, preview|removed}` shape; both paths render
correctly.
- **`pica_notes_list` / `pica_notes_query` silently returned an empty
list on every call** (same envelope-unwrap class, found in the sweep).
`GET /admin/notes` nests `{data, count, pagination}` under `data`; the
tool's bare-array check discarded the object. Now unwraps the inner
`data` array.
### Changed
- Deprecation `removal_target` on the 16 ADR-149/tool-consolidation aliases
in `TOOL_ALIASES` / `TOOL_METADATA` extended from `2.92.0` to `2.93.0` —
this release reaches `2.92.0`, so the prior target would have tripped the
ADR-180 deprecation-expiry lint. No removal decision made; purely
housekeeping to keep the aliases alive one more minor.
## [2.91.2] - 2026-07-09
### Added
- **WS3 "Confident Recall" — ambiguity signalling on `pica_search_all`.** When the top fuzzy matches
are too close to call, the response carries an additive `ambiguity` block
(`ambiguous: true`, the close candidates with confidences, and guidance)
and the text content leads with an "⚠ AMBIGUOUS MATCH" banner — the agent
is told to ask the user which entity they mean instead of acting on a
guess. Absent on clear wins; identifier-exact lookups (ISRC/ISWC/IPI)
never signal.
- **`pica_people_inspect` gains a `connections` section** — the one-call
org-catalog neighbourhood of a person: works, recordings, releases, and
agreements as `{kind, id, name, via}` with per-kind counts and a capped
flag. Answers "everything connected to X" / "what have I done with X"
without stitching sections. Creator Question Atlas backing added;
`pica_graph_traverse` (global graph, identifier-only) cross-references it.
- **Accent parity, last mile.** The releases free-text query and the
collaborator/contributor searches now match accent-insensitively
("fez" finds "Féz"), completing the parity the catalog entity searches
gained on 2026-07-06.
## [2.91.1] - 2026-07-08
### Added
- **`pica_tool_details` now returns an `invoke` pointer on every tool detail**
(single-tool and category paths). On the layered 3-tool surface the wrapped
tool is not in the caller's function list; models that fetched a schema and
found no matching callable concluded the tool was unavailable and gave up
(observed on ChatGPT). The `invoke` field spells out the execution path:
run via `pica_execute` with `{"tool": "<name>", "args": {...}}`.
- **`pica_resolve_person` / `pica_resolve_work` are now natural-language
creator entries** ("find more information about this person / this song",
"enrich this…", "look this song up in the MLC"), with Creator Question
Atlas backing. Flipped from specialist after founder telemetry showed the
ask arriving in exactly those words and neither resolver surfacing.
## [2.91.0] - 2026-07-07
### Added
- **The Oracle (Workstream 2) — count / rank / aggregate.** `pica_catalog_stats`
now accepts `entity` (works / recordings / releases / people) and `group_by`,
answering "how many have I produced", "who's my top collaborator", "genre
breakdown", "releases per year" in one call. No args returns the flat
multi-entity overview exactly as before (backward-compatible); `entity` alone
returns that entity's total (now including releases); `entity` + `group_by`
returns ranked buckets. Backed by org-scoped `GROUP BY` and a corrected
distinct-work collaborator ranking. No new tool — the existing counts tool is
enriched, and it is now surfaced as a natural-language creator-entry.
## [2.90.0] - 2026-07-07
### Changed
- **Tool-surface consolidation (Workstream 5).** Overlapping tools now sit behind
one canonical front door each, via a silent-alias engine: deprecated names stay
callable (they route to the canonical tool and carry a `_meta.deprecation` hint)
but are hidden from `pica_discover` and `tools/list`. **No breaking changes** —
every old name keeps working; the agent-facing surface simply shrinks.
- Attach cluster folded 4→2: `pica_releases_attach_track` is now a superset
(single track by `recording_id`/`work_id`, by external `identifier_type`+`value`,
or a batch via `items[]` with `dry_run`); `pica_releases_attach_track_by_identifier`
and `pica_releases_bulk_attach_tracks` are deprecated aliases of it.
- Read tools aligned to the `query`/`inspect` pattern for releases, projects, notes,
sessions, documents, statements, and share_links; legacy `list`/`get`/`read` names
are deprecated aliases.
- `pica_recording_credits_update` accepts a `credits[]` batch; `pica_recording_credits_bulk_update`
is a deprecated alias. The dead `audio_share` mode was removed from `pica_send_message`
(use `pica_share_send` for asset sharing).
### Deprecated
- 16 tool names are now silent aliases (hidden from discovery, still callable). They
will be removed in a future minor once traffic drains; callers should migrate to the
canonical names surfaced by `pica_discover`.
## [2.89.0] - 2026-07-06
### Changed
- Picks up `@withpica/mcp-sdk` 3.4.0: `pica_recordings_query` free-text search now
resolves through the server-side unified matcher, so recordings are findable by
`artist_name`/ISRC, not just title (and without the 200-row client-filter
ceiling). No tool-surface changes.
## [2.88.2] - 2026-07-05
### Changed
- `pica_search_all` now accepts `q` as an alias for `query`; `pica_recordings_inspect`
accepts `isrc` and `pica_works_inspect` accepts `iswc` as entry points (each
resolved to the entity server-side by exact match on the normalized code,
`NOT_FOUND` when nothing carries it). All three are enforced via
`requiredOneOf`, so a `{q:"..."}` / `{isrc:"..."}` / `{iswc:"..."}` call is no
longer hard-rejected by the `pica_execute` dispatcher with
`WRAPPED_ARGS_MISSING`. Fixes the in-app assistant roadblock of 2026-07-05,
where the model repeatedly called search with `q` and inspect with `isrc`; the
rejections cascaded into hallucinated-UUID `NOT_FOUND`s because it never
resolved the real ids. Resolvers query the search backend with the normalized
(dash/dot-free) code so a formatted `UK-JUX-17-00002` / `T-034.524.680-1` still
matches the dash-free stored value.
## [2.88.1] - 2026-07-04
### Added
- `TEMPLATE_RENDER_FAILED` recovery hint for `pica_agreements_create_from_template`
— the backing route now returns a typed 400 (bad `template_id` / missing
required variables) instead of a generic 500, so the tool surfaces an
actionable next step (query templates via `pica_agreement_types_query`).
### Removed
- `expires_in_days` on `pica_agreements_create_from_template` (it was a
documented no-op). A draft has no signing window; set the expiry at
`pica_agreements_send_for_signature`, which keeps its own `expires_in_days`.
## [2.88.0] - 2026-07-04
### Added
- `pica_agreements_create_from_template` — the missing entry point of the
e-signature flow. `pica_agreements_send_for_signature` (ADR-289 Wave A)
could email a draft agreement's required signers, but no tool could
create that draft WITH `required_signers` set — the backing route,
`POST /admin/agreements/create-from-template`, was the only API path that
accepts it, and had no MCP tool at all. Renders an agreement template
against real variables and creates the resulting agreement in `draft`
status; success message names the agreement id, points at
`/inspect/agreements/<id>`, and recommends
`pica_agreements_send_for_signature` as the next step.
### Changed
- MCP discoverability wave (ADR-289 follow-up): reworked `pica_discover`
ranking so the right tool wins each query class — importing a
Spotify/ISRC link now routes to `pica_import_streaming_link`, and money
questions route to the tool that actually answers them. Added a
`pica_discover` query filter and surfaced gate visibility in
`pica_tool_details`; rewrote the 15 worst tool descriptions with jargon
glosses; swept stale copy (real tool count, free-runway language); purged
phantom tool-name references and added a lint that blocks them permanently.
## [2.87.0] - 2026-07-04
### Added
- ADR-289 port wave: `pica_agreements_send_for_signature` (human-approval gated),
`pica_bookings_query`, `pica_bookings_update`, `pica_projects_participants`,
`pica_projects_multimedia`, `pica_artist_projects`, `pica_people_triangulate`,
`pica_memory_update`, `pica_telegram_config`, `pica_enrichment_clear`,
`pica_send_cancel`.
- Extensions: `pica_works_inspect` licensing section; `pica_agreements_inspect`
signatures section; `pica_agreements_query` awaiting_signature filter;
`pica_agreements_link_work` detach; counter-offer fields on
`pica_update_license_enquiry_status`; `pica_import_youtube_link` refresh;
`pica_projects_attach_works` detach; org inbound address in
`pica_statements_pending`; file-request fields on `pica_send_message`.
### Changed
- Tool scopes aligned to their route floors so the in-app assistant's grant can
call them (`pica_bookings_*`, `pica_update_license_enquiry_status`,
`pica_send_message`, `pica_send_resend`, memory tool family).
## [2.86.3] - 2026-07-03
### Fixed
- **`pica_enrichment_candidates`** — the tool failed on every call with
MCP -32602 "Invalid tools/call result: expected record, received array":
the completeness endpoint returns a bare list and `formatAsText` emitted
it as an array `structuredContent`, which the MCP spec forbids. Fixed at
the shared formatter (`@withpica/mcp-utils`): list payloads are wrapped
in the standard `{ items, count }` envelope.
## [2.86.2] - 2026-07-03
### Fixed
- **`pica_people_query`** — the `offset` parameter is now honoured on
unfiltered (no query/filter) calls: paginated requests route through the
search path, which forwards offset and returns a real `total`. Previously
the plain-list path silently dropped offset and every page request
re-served page one, making catalogs larger than one page unreachable.
- **`pica_audio_identify`** — a `NOT_FOUND` now carries a recovery hint
naming the id class (identify takes an AUDIO FILE id, not a recording or
work id) and steering to `pica_audio_query`, instead of leaving agents
retrying stale ids blind.
## [2.86.1] - 2026-07-03
### Fixed
- **`pica_execute`** — an unknown wrapped tool name is now refused
pre-dispatch with `UNKNOWN_TOOL`, the three closest registered tool
names as `suggestions`, and a pointer to `pica_tool_details` — replacing
the bare `EXECUTION_ERROR "Tool not found"` that left agents retrying
guessed name variations blind. The `EXECUTION_ERROR` pass-through
envelope also mirrors its code/message into `structuredContent`.
- **Audit telemetry** — dispatcher and executor error envelopes returned as
content-text JSON (without `structuredContent`) now have their
`error_code`/`error_message` lifted into the mcp audit event, so
error-grouping pipelines see a code instead of an opaque error row
(26 code-less ChatGPT dispatcher failures in 14 days were invisible to
ops triage).
## [2.86.0] - 2026-07-02
### Changed
- **`pica_merge_duplicates`** — now `approvalMode:"human"` (ADR-288): a
delegated-agent caller (OAuth connector / `pica_grant_*`) no longer gets
refused — the call freezes into a pending human approval, the org owner
approves out-of-band, and PICA replays the frozen merge server-side.
Blocked merges (custody provenance, distinct-ISRC losers, tunecode
collision) fail the approval cleanly with the blocking reason and zero
partial mutation. Resource scope corrected `admin` → `write:catalog`
(matching the backing route) so catalog-scoped delegated agents can
propose; first-party callers still need the destructive tier as before.
## [2.85.1] - 2026-07-01
### Changed
- **`pica_releases_create_with_tracks`** — accept `parent_label` on the nested
`release` object, completing the `parent_label` write path (create + update
landed in 2.85.0). The RPC and service already persisted it; the tool schema
omitted it, so the atomic-create path silently dropped the field (agent
feedback `eee77119`).
## [2.85.0] - 2026-07-01
### Added
- ADR-286 Phase 0 royalty-reconciliation tools: `pica_royalties_unmatched`, `pica_royalties_attribute`.
- `pica_documents_delete` (clear a failed-to-parse / superseded document + its storage object).
### Changed
- **`pica_releases_create` / `pica_releases_update`** — accept `parent_label`
(free-text parent/umbrella label name). It is a real `releases` column but was
absent from the write schema, so agent writes were silently dropped while the
call reported success (agent feedback `eee77119`).
## [2.84.0] - 2026-06-30
### Added
- **`pica_audio_update`** (#1379) — set `file_type` / `stem_label` /
`version_label` / `title` on an audio file after upload, wrapping the
org-validated `PATCH /audio-files/[id]` route.
### Changed
- **`pica_audio_query`** (#1378) — unassigned audio can now be filtered by
filename/title, making discover-and-assign workflows precise on large
upload sets.
## [2.83.2] - 2026-06-27
### Added
- **`pica_statement_ingest_document`** (`write:finances`, `approvalMode:"human"`) —
agent-driven royalty/streaming statement ingestion for non-CSV statements
(PDF/image/xlsx). The agent reads the document, extracts the line items, and
calls this tool; for a delegated (OAuth/grant) caller it does NOT write — it
raises a human-approval request, and only the org owner's approval replays it
into `royalty_payments`, reusing the existing CSV parser downstream (identity
resolution, currency normalisation, fingerprint dedup). CSV statements stay on
the deterministic importer (PICA #1332).
- **`pica_statements_pending`** (`read:finances`) — lists uploaded
`royalty_statement` documents the user hasn't imported yet (lean summaries, no
financial figures), so the agent can discover them; routes to
`pica_statement_ingest_document` (PICA #1332).
- **`pica_audio_assign`** (`write:files`) — attach an uploaded audio file to a
work and/or recording after the fact; pairs with the new
`pica_audio_query unassigned:true` filter so an agent can discover orphan
audio and give it a home. Wraps the existing org-validated
`PATCH /api/admin/audio-files/[id]`; no approval gate (low-risk, reversible)
(PICA #1337).
### Changed
- **Society-agnostic reads (ADR-278).** Bumped `@withpica/mcp-sdk` to `^2.0.0`,
whose read types replace the `prs`/`ppl`/`mlc` registration scalars with a
single `society_registrations[]` array, and updated the `pica_works_query`
tool descriptions so they no longer privilege PRS/PPL over other collection
societies. No tool was added or removed and no handler logic changed; this is
a republish so external stdio consumers receive the BREAKING SDK read shapes
already live on the hosted `/api/mcp`. Consumers reading `prs`/`ppl`/`mlc`
fields must migrate to `society_registrations[]`.
- **`pica_audio_query`** — new `unassigned` boolean filter returns audio with no
work and no recording (PICA #1337).
- **`pica_documents_read`** — description now tells the agent to STOP retrying
when `parsing_status` is `failed` and read the new `parse_error` field for the
reason (e.g. a legacy `.xls` that must be re-saved as `.xlsx`), instead of
polling a permanently-failing parse. Pairs with the server-side fix that makes
the read return an honest `failed` + `parse_error` rather than looping in
`processing` forever (ops_issue 8e197fe5). No tool/schema/SDK change.
- **`pica_documents_read`** — also points the agent at
`pica_statement_ingest_document` when the document is a royalty/streaming
statement (PICA #1332).
## [2.83.0] - 2026-06-25
### Added
- **`pica_resolve_release`** — release-grain Spotify resolver (scope
`write:catalog`). Gap-fills `upc`, `release_date`, `artwork_url`, `title`,
and `parent_label` from Spotify when `spotify_album_id` is set; routes every
write through the authority gate so human / higher-authority / unprovenanced
values are never overwritten. Idempotent (second call → `applied=[]`).
Spotify failure lands in `errors[]`, never throws. No `spotify_album_id` →
no-op + `recovery_hints`. v1 Spotify-only; additional sources in future ADRs.
New: route `POST /api/admin/releases/[id]/resolve`, SDK `resolveRelease`.
- **`pica_catalog_health`** — agent-first no-arg whole-catalog **completeness +
cleanliness** verdict (score + ranked punch-list across works / recordings /
releases / people). Honest-zero: a check that throws is reported
`notAssessed`, never a silent pass. Org-scoped, bounded sample ids. (ADR-272)
- **`pica_catalog_health_plan`** — derives an actionable fix-plan from the
health verdict: concrete fix-actions tagged safe/needs-scope plus a residual
list of what can't be auto-fixed, returned with a `planToken`. (ADR-273)
- **`pica_catalog_health_fix`** — applies accepted plan actions (a list of
`actionIds`, or `all_safe_auto`) against a `planToken`; reports honest
`applied` / `skipped` and never reports a partial apply as success. (ADR-273)
- **`pica_documents_list`** — lists uploaded catalog documents for the org
(parsed status + lean projection). ADR-274 document bridge.
- **`pica_documents_read`** — scope-gated bounded read of a parsed document's
extracted content (`offset` / `max_chars`). ADR-274 document bridge.
- **`pica_documents_link`** — stamps a parsed document's provenance link to a
`work` / `person` / `recording`. ADR-274 document bridge.
### Changed
- **`pica_recordings_update`** now accepts a free-text `notes` field (master-grain
annotation, parity with `works.notes`).
## [2.82.0] - 2026-06-22
### Added
- **`pica_releases_delete`** — delete a release (scope `write:catalog`, tier
`destructive`). Refuses with the structured blocking list when the release
still has tracks attached or active share links (releases have no soft-delete);
detach the tracks and revoke the links, then retry.
- **`pica_audio_delete`** — delete an audio file (scope `write:catalog`, tier
`destructive`). Removes the file record AND its underlying storage object — no
orphan is left behind — along with its analysis/transcription satellites. Does
not touch the parent work or recording.
### Fixed
- **`pica_merge_duplicates` (works)** now refuses a merge that would collapse
distinct recordings: when the winner and a loser each link a recording with a
different ISRC, the merge is blocked with `MERGE_BLOCKED_DISTINCT_RECORDINGS`
(and a recovery hint) instead of destroying the loser work. Prevents the
title-only-duplicate data-loss class.
- **`pica_people_update`** now accepts `is_group` (boolean) — previously
rejected as an unknown field, forcing the `group` tag and boolean to drift.
## [2.81.0] - 2026-06-19
### Added
- **ADR-270 §3.4 leverage signals on `pica_works_query` + `pica_recordings_query`**: `streaming_activity` filter / sort / on-row (rank by traction — "what's worth my time"), and `missing_field_count` filter + sort on `pica_recordings_query` (the `pica_works_query` equivalent shipped in 2.80.0). Income stays agent-composed, never a field.
## [2.80.0] - 2026-06-17
### Added
- **`pica_register_set`** — one-call DAW set registration (ADR-259). Creates a
work, a linked master recording, and a 100% org master ownership split in a
single call via `POST /api/admin/register-set`, replacing the five sequential
calls (`pica_works_create` + `pica_recordings_create` +
`pica_recording_splits_create`, plus the dup pre-check and splits list). Used
by the Ableton extension's register flow to collapse five serial round-trips
into one. On a title+artist duplicate it fails with the existing work id so the
caller can add a version or complete the existing work.
- SDK `register.set()` (`@withpica/mcp-sdk` 1.45.0).
## [2.79.0] - 2026-06-15
### Fixed
- **`pica_execute` annotations** now advertise `readOnlyHint: false` and
`openWorldHint: true`, matching the dispatcher's real behaviour — it can route
to write/destructive/external tools, so claiming read-only / closed-world was
wrong (and was flagged in OpenAI app review). The scope `tier` stays `"read"`
so a read-scoped key can still dispatch read tools through it; real scope and
destructive confirmation remain enforced on the **wrapped** tool, not the
wrapper. Implemented via a new advisory-only `annotationOverrides` field on the
tool definition (completes the ADR-230 tier→hint coupling for the one tool
whose client-facing hint must differ from its scope tier). No enforcement
change.
## [2.78.0] - 2026-06-15
Carries the uniform deletion contract MCP rendering (originally 2.76.0) forward
on top of 2.77.0's `pica_import_from_spotify` removal. 2.76.0 (deletion contract)
and 2.77.0 (import removal) were published from parallel branches; 2.77.0 was
built before this branch merged, so it did not include the deletion rendering —
this release re-establishes it on top of the removal.
### Changed
- **`pica_works_delete` / `pica_recordings_delete`** render the uniform delete
outcome via `formatDeletion` (status `deleted`/`archived`/refused + what was
unlinked + how to fully delete); descriptions state the contract. Re-applied
here after 2.77.0 (parallel branch) shipped without them.
## [2.76.0] - 2026-06-15
Uniform deletion contract surfaced to agents. `pica_works_delete` and
`pica_recordings_delete` now report whether the entity was deleted or archived,
what was auto-unlinked, and — when refused — exactly what to remove first.
### Changed
- **`pica_works_delete`** description states the contract (auto-unlinks from
releases; archives instead of destroying when it has identifiers/dependents);
result renders via `formatDeletion` (status + detached).
- **`pica_recordings_delete`** description states the contract (credits/splits
removed with it; refused with a blocking list if it is a release track / has
payments / is in a sync placement); result renders via `formatDeletion`.
- **`pica_works_bulk_delete`** description notes each work follows the same rule.
- **`pica_releases_list`** gains `query` (title substring), `upc` (exact), and
`spotify_album_id` (exact) lookup filters — locate a release by
title/UPC/album instead of paging the whole catalogue (feedback 9a7a50f7).
## [2.77.0] - 2026-06-15
### Removed
- **`pica_import_from_spotify`** (BREAKING) — removed after a multi-release
deprecation window. It was superseded by **`pica_import_streaming_link`**,
which offers the same two-step preview/confirm flow and output and also
supports Deezer. Callers passing a Spotify URL should switch to
`pica_import_streaming_link` with the `url` argument (the only rename is
`spotify_url` → `url`). Shipped as its own deliberate breaking PR rather than
bundled into an unrelated release, per the standing removal plan recorded in
`TOOL_METADATA`. (2.76.0 = the uniform deletion contract, shipped separately.)
## [2.75.0] - 2026-06-15
### Added
- **`pica_work_writers_add`** — add composition writers to a work by name (ADR-259 DAW writer capture). Each name is resolved to a person (exact-match an existing person, else auto-create a tagged writer person) and **additively** added as a `role='writer'` collaborator — existing collaborators are never replaced. Percentages are not set (writer shares reconcile via attestation); ambiguous / multi-name entries are skipped and reported. Runs under `write:catalog`.
## [2.74.0] - 2026-06-14
### Added
- **`pica_recordings_bulk_reassign_artist`** — bulk-repoint recordings'
`primary_artist_id` in one atomic call (by explicit `recording_ids` or by
`from_person_id` to drain a junk "magnet" person; `to_person_id` null
clears). Runs under `write:catalog` — no destructive scope needed, because
it only re-points the structured link (the `artist_name` label is
untouched). Returns `source_now_empty` so a drained junk person can be
retired. The fix for the "unknown" artist magnet class.
- **`clean-up-artists` skill** — guided junk-artist cleanup: find magnets,
build a plain-language repoint plan from each recording's own `artist_name`,
preview, execute via the bulk tool, retire emptied rows.
- **`primary_artist_id` on `pica_recordings_update`** — repoint a recording
to the correct in-org person (the service already validated it; the tool
param was missing).
### Changed
- Route validation errors now surface their real code + message to agents
instead of an opaque `UNKNOWN_ERROR` (via `@withpica/mcp-utils` 1.23.1).
## [2.73.0] - 2026-06-12
ADR-264 WS2 verify-before-assert + the WS4 front-door prerequisite (bulk
recording credits) + ADR-268 WS1 party claims / WS2 identifier-based party
dedup / WS5 ownership transfer types + the ADR-267 calibrated-question voice
contract. Also removes the never-functional `pica_team_*` tools and the
long-deprecated `pica_dashboard_pulse`.
### Added
- **`pica_party_claim_invite` + `pica_party_claim_status`** — ADR-268 WS1
party org claim spine. `pica_party_claim_invite` sends an artist-anchored
claim invite for an unclaimed label/distributor/publisher organisation
(the user-supplied recipient email is the consent + identity anchor; the
single-use link redeems on the public claim landing — redemption makes the
claimant the org's owner, flips verification pending→verified and billing
hibernated→trial, and mints an active org-to-org connection with the
inviting catalog, never membership). Concurrent claims freeze both sides
as `disputed` for founder adjudication, and the tool says so instead of
pretending the email went out. `pica_party_claim_status` lists the claims
the calling org has sent with their lifecycle status. Registered in the
`labels` discovery category; tool count tripwire 282 → 284.
- **`pica_recording_credits_bulk_update`** — ADR-264 WS4 prerequisite. Apply
one credit set across many recordings in a single call: target a release
(`release_id` — every recording on its tracklist) or an explicit
`recording_ids` list, plus a `credits[]` batch (`credited_name` + `role`
per entry, optional `person_id` / `split_percentage` / `split_type` /
`notes`). One album's production credits stops being 30 sequential
per-recording calls. Existing (recording, credited name, role) rows are
skipped, never overwritten — re-running is safe; `dry_run: true` previews
the exact per-row `would_create` / `would_skip_existing` decision set the
execute path uses. Validation is all-or-nothing with per-problem detail
(invalid input applies NOTHING); application returns per-row
`created` / `skipped_existing` results. Caps: 100 recordings, 50 credits,
500 total rows per call. Backed by the new
`POST /api/admin/recording-credits/bulk` route (org-scoped at every step:
release, tracklist, recordings, and person_ids must all belong to the
caller's organisation).
- **`pica_works_verify_registrations`** — ADR-264 WS2 truthful diagnosis. Verify
the registration status of up to 25 works. `mode: "check"` looks each work up
at the MLC (ISWC → title + writer IPI → title + primary-artist name) and
records what the lookup PROVED: a found registration is written as a positive
society fact (and any stale negative assertion is cleared); a clean not-found
becomes a `verified_absent` assertion with a lookup receipt. Lookup errors and
works with no usable identifiers write NOTHING (`lookup_failed` /
`insufficient_identifiers`). `mode: "confirm_unregistered"` records the user's
own confirmation (optional `society_code`; omitted = "not registered with any
society"). `works_without_registrations` rows on `pica_dashboard_attention`
now carry an `evidence` block (`unverified_absent` / `verified_absent` /
`user_confirmed_absent` with `verified_via` + `checked_at`) reflecting these
assertions.
- **`unconnected_collaborators`** attention class (taxonomy gap, f0266f09
gap #6): people credited on the org's works with no linked PICA account now
appear on `pica_dashboard_attention` / briefing totals / `pica_count_explain`
— one shared compute with `briefing.unconnected_collaborators` so the
surfaces structurally cannot drift. drill_down_skill null until a skill
covers collaborator connection.
- **Decision blocks (calibrated questions)** — `pica_find_duplicates` groups,
the `pica_merge_duplicates` MERGE_BLOCKED_PROVENANCE error, and
`pica_works_verify_registrations` mode:"check" (when works couldn't be
verified) now carry a `decision` field: a question with 2-3 options,
plain-language consequences, and a safe default — no UUIDs in any decision
text (a UUID-shaped title degrades by omitting the block, never failing the
call). Always additive: the structured payload carries everything the block
says. `pica_dashboard_briefing` now carries `user_message` — one
ready-to-say lowercase sentence in the curiosity register (no loss framing
on unverified counts).
### Changed
- **`pica_recording_custody_assign`** — ownership transfer types are live
(ADR-268 WS5, the ADR-262 fast-follow): `transfer_type` now accepts
`assignment` (title transfers) and `full_transfer` (ownership AND control)
alongside the v1 control/possession types. An ownership leg also moves the
recording's master ownership splits append-only in the same transaction —
the transferor's active era closes (`end_date`), the transferee's opens —
and the credit layer never moves. Honest-gap refusals (400, typed codes):
a partial percentage with more than one active owner
(`PARTIAL_TRANSFER_AMBIGUOUS`), a transfer exceeding the transferor's
share (`INSUFFICIENT_TRANSFEROR_SHARE`), or a percentage outside (0,100]
(`INVALID_PERCENTAGE`) — nothing is ever silently lost.
- **`pica_labels_create`** accepts an optional `identifiers` object
(`musicbrainz_label_id` / `isni` / `label_code` / `discogs_label_id` /
`wikidata_id` / `deezer_label_id`) — ADR-268 WS2. Dedup is now
identifier-first: supplied identifiers resolve to the global label identity
and an org already bridged to that identity is returned
`already_existed: true` EVEN when the supplied name differs;
case-insensitive name matching (ADR-265 behaviour) stays as the fallback,
and a name-matched org missing the bridge is backfilled when an identity
was resolved. Param addition only — no new tool.
- **`pica_recording_credits_update`** accepts an optional `instrument`
param (ADR-259 Stage 2) — the played instrument(s) for this credit, e.g.
`'drums'` or `'drums, bass'` (one credit per person; multiple instruments
list on the same row). Stored on `recording_credits.instrument`, rendered
in `/inspect`'s recording contributors table, and written by the Ableton
extension's attribution checklist. Server-side ADR-255 resolve-on-write
now also fires for ANY recording credit arriving without a `person_id`:
an exact org-scoped name match links it, anything else stays a free-text
draft (never auto-creates a person). Param addition only — no new tool.
- **`first-session` skill** now carries the calibrated-question library (ADR-267
Step 1): open-over-closed decision questions, diagnosis labels, no-oriented
phrasing for destructive moves, options-with-consequences, evidence-gated
stakes, and a no-UUIDs-in-user-facing-text contract. Specialist skills adopt
the same grammar incrementally; the WS4 front-door skill embeds it at birth.
- **`pica_works_create` / `pica_works_update` `mood` param description** now
states the value is stored on the work's primary recording's audio traits
(recording-grain, ADR-266 Stage 2c) — caller contract unchanged.
### Fixed
- **Review-link contract** — server instructions told every agent to share
`withpica.com/workspace?type=<entity>&id=<id>` after writes; now the
schema-direct `/inspect/<feature>/<id>` shape (both byte-parity copies).
uploads/audio/exports tool descriptions point users at `/inspect`.
### Deprecated
- **`pica_import_from_spotify`** — use `pica_import_streaming_link` (same
two-step flow, also supports Deezer). Structured
`{deprecated, removal_target: "2.74.0"}` in TOOL_METADATA; the new
lint-mcp-tools deprecation-expiry check BLOCKS any publish at or past the
removal target, so this cannot overstay like pulse did.
- **`briefing.unverified_writers`** count source — `briefing.unenriched_writers`
is the canonical name (matches the actionable-issues taxonomy class); the
alias shares one definition and is accepted until 2.74.
### Removed
- **`pica_team_*` (all 5: `pica_team_list` / `pica_team_get` / `pica_team_invite`
/ `pica_team_update_role` / `pica_team_remove`)** — founder decision
2026-06-11. The backing routes (`/api/admin/team/*`) are session-only BY
DESIGN (ADR-248: team membership is human-only), so every bearer/MCP caller
has always received a hard 401 — the tools never worked for any agent and
never could. Removing them is honesty, not a regression: agents stop being
advertised a capability that structurally cannot succeed. Team membership
stays managed by humans in `/settings`. Also removed: the `team` discovery
category, the `pica_team_remove` destructive-confirmation case, recovery
hints, cost-model entries, and the `set-up-workspace` skill's Block 5 now
points users at `/settings` instead of the tools. Tool count tripwire
287 → 282; destructive tripwire 20 → 19.
- **`pica_dashboard_pulse`** — deprecated since ADR-247 with a removal window
that passed (prod telemetry: 3 calls all-time, exploratory).
`pica_dashboard_briefing` is the canonical attention surface; pulse's
revenue-at-risk/deadline predicates retired with it (they disagreed with
the briefing coherence contract). Tool count tripwire 288 → 287.
- **`suggested_skill`** alias dropped from `pica_dashboard_attention` rows
(deprecated 2026-05-22; soak window long passed; zero non-test consumers).
`drill_down_skill` is the canonical field.
## [2.72.0] - 2026-06-10
ADR-264 WS1.3 — recordings/releases dedup + merge (last trust-floor gap).
### Changed
- **`pica_find_duplicates`** — `entity_type` now covers `releases` and
`recordings` (added in 2.72.0); description explicitly names the UPC and
ISRC grouping keys so callers understand the detection basis.
- **`pica_merge_duplicates`** — extended to cover `recordings` and `releases`
(+ singular aliases `recording` / `release` for backwards compatibility).
- `recordings`: merges via `merge_recording_atomic` RPC — credits, splits,
and release-track links are deduped by their natural keys; winner's NULL
fields (ISRC, duration, streaming IDs, artist name, artwork) are back-filled
from the first loser that has a value.
- `releases`: merges via `merge_release_atomic` RPC — tracks are rehomed
(dedup by recording_id within the winner's tracklist); UPC, release date,
artwork, and label/distributor org links are gap-filled from losers.
- **PROVENANCE BLOCK**: a recording loser with `recording_custodians` rows
(ADR-262 append-only custody chain) blocks the merge. The tool returns a
structured `MERGE_BLOCKED_PROVENANCE` error with `blocked_ids`, the `remedy`
("custody chains are append-only; transfer custody first or keep both
recordings"), and `next_tool: "pica_recording_custody_assign"` so the AI can
route directly to the safe resolution path.
### Recovery hints
- `MERGE_BLOCKED_PROVENANCE` added to the `pica_merge_duplicates` hint registry
→ `pica_recording_custody_assign`.
## [2.71.0] - 2026-06-10
ADR-265 release parties and invites — label/distributor org creation + recording/release-grain invite targets. Requires `@withpica/mcp-sdk@^1.39.0`.
### Added
- **`pica_labels_create`** — create an unclaimed label or distributor organisation.
Accepts `name`, `org_type` (`label` | `distributor`), and optional `country` /
`website` / `contact_email`. Server-side deduplication is catalog-wide across
both party types: a case-insensitive name match returns the existing record
with `already_existed: true` rather than creating a duplicate. The returned
`id` can be assigned to `releases.label_organization_id` (via
`pica_releases_update`) or `releases.distributor_organization_id` to formally
link a release's label or distributor.
- **`recording_id` / `release_id` targets on `pica_collaborators_invite`** — in
addition to the existing `work_id`, invite calls now accept exactly one of three
target ids (`work_id` | `recording_id` | `release_id`). Recording-grain invites
surface a claim scoped to the named recording's credits; release-grain invites
surface a claim that verifies all the invitee's credits across every recording on
the release. Exactly-one-of validation is enforced server-side (400 if zero or
multiple targets are supplied).
### Fixed
- **`pica_labels_query` always returned "Found 0 labels"** — an SDK envelope
double-unwrap (`labels.list` read `.data` off the already-unwrapped array)
meant the tool had returned empty results regardless of catalog state since
it shipped (2026-04-14). Fixed in `@withpica/mcp-sdk@1.39.0`.
- **`pica_labels_create` dedup messaging now sees `already_existed`** — the
same envelope unwrap stripped the `already_existed` sibling flag, so every
successful create tripped the no-data guard and the "already exists — link
it instead" guidance never fired. The SDK now returns the label object
directly with `already_existed` inside it.
## [2.70.0] - 2026-06-10
ADR-264 WS1 trust floor — conservative-by-default enrichment, truthful tool
outputs, and the reviewable enrichment queue. Requires `@withpica/mcp-sdk@^1.38.0`.
### Added
- `pica_enrichment_proposals_bulk_reject` (mutating, `dry_run` preview):
reject up to 100 pending enrichment proposals by explicit ids OR a filter
(`rule_id` / `source` / `max_confidence`). Per-proposal effects identical to
`pica_enrichment_proposal_reject` (permanent content-hash suppression).
`dry_run` resolves the real affected set — the filter path queries the live
list, no fake previews.
- `pica_enrichment_proposals_list` gains `min_confidence`, `max_confidence`,
and `created_after` filters, so a caller can isolate the batch they just
triggered from accumulated stale low-confidence noise.
- `pica_releases_list` gains `offset` pagination with an honest envelope
(`count`, `offset`, `has_more`, `next_offset`) — every release is reachable;
no more mid-array ~100KB truncation dead-end.
### Changed
- Enrichment behaviour (backend, surfaced through proposals): official
YouTube " - Topic" channel uploads now classify as `master` and the
`artist_title_to_youtube` rule defaults to enriching the existing master
recording (UPDATE proposal attaching `youtube_video_id`/`youtube_url`)
instead of minting a phantom `alternate` recording; creating a new
recording now requires affirmative low-sameness evidence (version markers
or >3s duration delta). Proposals carry a `sameness_evidence` signal.
- `pica_import_from_spotify` preview wording: duplicates are reported as
"already in catalog (will be skipped, not modified)" — the preview no
longer promises an enrichment the executor does not perform.
- `pica_execute` hardening: the `WRAPPED_ARGS_MISSING` transport error now
fires when ANY required field of the wrapped tool is missing (partial
client-side argument strips included), naming `missing_required_fields`.
The nested `args` object is the canonical call shape.
### Fixed
- `pica_enrichment_proposals_list` no longer renders the literal
`"? pending proposal(s)"` when the backend returns an unexpected shape —
that case is now a structured `UNEXPECTED_RESPONSE_SHAPE` error (with
`structuredContent`), not a fake count.
### Removed
- Dropped the unused `mppx` runtime dependency. No `src/` consumer has existed
since the MPP unit tests were deleted; the earlier cleanup left it in place
"to avoid lockfile churn" and asked the next publisher to drop it — done.
## [2.69.0] - 2026-06-08
### Added
- Recording custody tools (ADR-262 — master-side control chain):
`pica_recording_custody_assign` (transfer custody of a recording; admin/owner,
control/possession transfer types only) and `pica_recording_provenance` (read
the ordered custody legs + immutable origin credits). Thin wrappers over the
live `/api/admin/recording-custody/*` routes via the new SDK
`recordingCustody` resource (requires `@withpica/mcp-sdk@^1.37.0`).
- `pica_releases_create` / `pica_releases_update` accept an `availability`
write param (`available` | `withdrawn`) to mark a release live or taken down.
## [2.68.0] - 2026-06-07
Adds a structured-metadata escape hatch to work creation. No SDK bump required (`@withpica/mcp-sdk` ^1.36.0).
### Added
- **`metadata` (jsonb) on `pica_works_create`** — callers can attach a free-form JSONB snapshot when creating a work (e.g. a DAW session capture: tracks, devices, samples, tempo). Stored verbatim on `works.metadata`; not individually queried.
## [2.67.0] - 2026-06-01
Enrichment field-level source ranking surfaces on the resolve tools. Requires `@withpica/mcp-sdk` ^1.36.0.
### Added
- **`force` on `pica_resolve_work` + `pica_resolve_recording`** — force a re-enrich past the per-source idempotence stamps. Safe by construction: the field-level authority gate never clobbers a human-entered, higher-authority, or pre-provenance (unprovenanced) value, so a forced re-run only fills gaps and upgrades lower-ranked automated values.
- **Overwrite receipts on both resolve tools** — the structured result now carries `overwrote[]` (a higher-authority source replaced a lower-ranked value, with old/new source) and `skipped_lower_authority[]` (a source was refused because the existing value out-ranked it, equalled it, or was a human/verified/unprovenanced value). The summary line reports the overwrote/kept counts.
### Changed
- **Resolve tool descriptions** now explain the authority gate (higher-authority sources win, human-entered and pre-provenance values are locked) and the two receipt arrays.
## [2.66.0] - 2026-06-01
ADR-250 human-in-the-loop approval goes live, plus the close-out batch carried by this release.
### Added
- **`pica_approval_status`** — read-only poll for an out-of-band human approval. When a delegated-agent destructive write is deferred, the dispatcher returns a `human_approval_required` envelope carrying an `approval_reference`; pass that as `approval_id` to check whether the grant owner has decided (`pending` / `approved` / `denied` / `expired` / `executed` / `failed`) and when/how. Ownership is enforced server-side — a foreign approval comes back as `not_found`, never a leak. ADR-250 PR-c.
### Changed
- **ADR-250 human-in-the-loop approval gate is active.** `pica_people_bulk_delete` now carries `approvalMode: "human"`: a delegated-agent caller proposes the deletion, the grant owner approves out-of-band, and PICA replays the frozen params server-side. Session and first-party-key callers fall through to the existing `dry_run`/destructive path unchanged. The tool stays connector-reachable rather than blanket-excluded.
## [2.65.0] - 2026-05-30
### Added
- **`pica_agreement_source_splits`** — reconcile the per-party splits you extracted from an uploaded agreement against a work's composition credits and source them if they match (±0.5% tolerance, 0.8 confidence gate). Outcomes: `sourced`, `needs_confirmation` (re-call with `confirm:true`), `discrepancy` (a finding, surfaced on `/inspect`). Split-source provenance phase 2. Requires `@withpica/mcp-sdk` ^1.35.0.
## [2.64.0] - 2026-05-30
ADR-252 WS-A surface alignment + the ADR-247 agent-hygiene crash fix. Requires `@withpica/mcp-sdk@^1.34.0`.
### Fixed
- **Agent-visible descriptions no longer name the dropped `work_credits` table.** ADR-252 WS-A dropped `work_credits`; composition credits now live on `work_collaborators` and master credits on `recording_credits`, but several tool/resource descriptions still described the old table (agents read these). Corrected across `pica_credit_add` hint (custody-hints), `pica_claim_credit` (now a status-only transition on `discovered_credits` — no credit row is written), `pica_claim_artist` (writes `recording_credits`), `pica_dashboard_attention` class glossary, `pica_recording_credits_update`, `pica_collaborators_accept`, the `pica_explain_count` glossary, the `pica_physical_assets_unlink_work` link-row label (now `recording_production_assets`), and the `work-credits-required` required-fields resource. Underlying write paths were already repointed in WS-A — this is description/accuracy only.
- **Dead tool pointer.** A custody gap-hint pointed agents at the non-existent `pica_work_credits_add`; corrected to the real `pica_credit_add`.
- **`pica_introduce_self` / `pica_outcome_log` / `pica_outcomes_recent` crashed on the happy path.** The SDK's `request()` unwraps the `{ success, data }` envelope and resolves to the inner `data`, but these three executors read `result.data.<field>` against the already-unwrapped object → `UNKNOWN_ERROR: Cannot read properties of undefined`. It surfaced rarely only because Bearer-key callers are rare (cookie/grant sessions are exempt from `introduce_self`). Root-caused in `@withpica/mcp-sdk@1.34.0` (return types corrected to the inner payload; `request()` now throws on a 2xx `success:false` body); the executors now read fields directly and guard the empty case. Added `agent-hygiene.test.ts` mocking the **inner** payload — the shape `request()` actually resolves to — which the prior coverage never exercised.
### Changed
- **`schema-mirror.json` refreshed off staging for the post-ADR-252 schema.** Replaced the dropped `work_credits` entry (`credit_type` / `rights_type` CHECKs no longer exist) with `work_collaborators` (only `attestation_status` is a CHECK; composition roles are validated tool-side), and added `recording_credits.attestation_status`. `refresh-schema-mirror.ts` `TABLES_TO_MIRROR` updated to match. The `work-credits-required` resource now surfaces only the `attestation_status` enum; valid role values come from the `role` field of `pica_credits_update`.
## [2.63.1] - 2026-05-25
Stdio/HTTP instruction parity fix.
### Fixed
- **Stdio `SERVER_INSTRUCTIONS`** now carry the ADR-247 first-call identity paragraph (Bearer API-key callers must call `pica_introduce_self` first). The paragraph had been added to the HTTP `/api/mcp` transport copy in 2.62.0 but not the published stdio binary, so stdio agents never saw the introduction-gate guidance even though the gate is enforced server-side. Restores parity between the two transports (enforced by the `mcp-server-instructions-parity` test).
## [2.63.0] - 2026-05-25
ADR-245 (composition surface) recording-session metadata + a work-export correctness fix.
### Added
- **`pica_recordings_*` recording-session metadata** — `pica_recordings_create` / `_update` accept, and `pica_recordings_inspect` / `_query` return, `recorded_at` (ISO date) and `studio_name` (text). ADR-245 §3.3. Engineer remains a `recording_credits` role; live remains `version_type: "live_performance"` — neither is promoted to a recordings column.
### Fixed
- **NRO/PPL export** no longer selects four columns that exist on no table (`ppl_artist_name`, `ppl_p_date`, `ppl_p_name`, `recording_date`) — they made the recording fetch return Postgres `42703` and broke the export for any work with recordings. Performer rows are now sourced from `works.recording_contributors` (their real home).
## [2.62.0] - 2026-05-24
ADR-247 (Agent Hygiene) sprint. After this version, every OAuth-Bearer / raw-API-key caller must declare itself via `pica_introduce_self` on first use, and every successful write-tier tool call is durably recorded in `workflow_outcomes` (the symmetric counterpart to `ops_issues` for errors).
### Added
- **`pica_introduce_self`** — first-call identity declaration. Required for OAuth-Bearer and raw-API-key callers; cookie + `pica_grant_*` callers are carved out at the router (their identity is on file at the auth boundary). `tier: "write"`. Inputs: `agent_name` (required), `agent_role`, `on_behalf_of`, `model_hint`. Declaration is valid 24h per credential; the router's hard introduction gate returns `412 IDENTITY_REQUIRED` until this lands. Backed by `AgentIntroductionsResource.declare` in `@withpica/mcp-sdk@1.33.0`.
- **`pica_outcome_log`** — agent-explicit workflow / milestone log. `tier: "write"`. Inputs: `kind` (required free-form — typical: 'workflow_completed' / 'milestone_hit'), optional `entity_refs[]` (UUID-validated server-side), `summary`, `agent_summary`. Throws on failure (unlike the auto-fire path which is fire-and-forget) so the agent gets useful feedback. Backed by `WorkflowOutcomesResource.log`.
- **`pica_outcomes_recent`** — read recent workflow outcomes for the current org. `tier: "read"`. Returns rows ordered by `last_seen_at DESC`, signature-deduped (repeat workflows collapse with `occurrence_count` incrementing). Captures both the auto-fire path (every write-tier MCP success) and the agent-explicit path (`pica_outcome_log` calls). Optional `kind` / `source` filters. Default limit 20, max 100.
### Changed
- **`pica_dashboard_briefing` response gains an `agent_velocity` block** when the org has workflow_outcomes activity in the trailing 7d. Shape: `{window_days: 7, count_7d, by_kind, top_tools: [{tool_name, count}]}`. Coherence contract: `count_7d == sum(by_kind values)`. Complements the existing `agent_activity` block (which is mcp_audit_log call counts by tier) — same window, different lens: `agent_activity` is "how many calls", `agent_velocity` is "what workflows actually completed / refused / nudged".
- **`pica_dashboard_pulse` deprecated.** Still callable (backwards-compat for N+2 releases) but `vernacular_kind` dropped from `creator-entry` to `specialist`. The Creator Question Atlas resolver for "give me the pulse" / "how's my catalog" now routes to `pica_dashboard_briefing` instead. Tool description prefixed with the deprecation notice + redirect. Deadlines/revenue/background-task signals pulse uniquely carried will land as `pica_dashboard_velocity` in a future ADR.
## [2.61.0] - 2026-05-23
### Changed
- **`pica_provenance_certificate` is now actually callable.** The tool's description has always been "Generate a provenance certificate", but the underlying SDK call did `GET /admin/works/[id]/certificate`, which 404s on any work without a pre-existing certificate. After bumping `@withpica/mcp-sdk@1.32.0`, the tool's executor POSTs to the same path and generates the certificate row. Closes ops_issues c3e64ee2 (NOT_FOUND, 2 events) and b2dddfee (AUTHENTICATION_ERROR, same caller, pre-OAuth-refresh flake). Description updated to disclose that each call writes a new row.
### Added
- **`pica_provenance_certificate_get`** — companion tool for the read path. `tier: "read"`, `risk: "safe"`, fetches an existing certificate row without regenerating. Returns 404 NOT_FOUND if no certificate has been generated yet; callers fall back to `pica_provenance_certificate` in that case. Registered alongside the existing generator in `analytics`, `metadata`, `recovery-hints`, `discovery`, and `public-filter`.
## [2.60.0] - 2026-05-23
Exports release. The four catalog export tools accept per-work scope, the catalog export tool's schema matches what the backend actually validates, and `pica_execute` is forgiving of the three call shapes agents emit in the wild.
### Added
- **`work_ids: string[]` on four export tools.** `pica_export_song_registration`, `pica_export_industry_ready`, `pica_export_ai_consent`, and `pica_export_catalog_asset_report` now accept an optional `work_ids` array. Omitted = whole catalog (existing behaviour). Provided = export limited to that set of works. Tool descriptions disclose the per-tool scoping semantics — `ai_consent` and `catalog_asset_report` apply the scope where it is structurally meaningful (recording rows on ai-consent, placements on CAR) and document the partial-scoping cases so agents don't pass `work_ids` expecting whole-row filtering.
- **`pica_execute` accepts FLAT and `arguments`-alias call shapes.** The dispatcher resolves wrapped args from the first non-empty of `args` / `arguments` / FLAT top-level keys. Canonical remains `{ tool, args: {…} }`; the other two shapes are tolerated because agents emit them in practice. `arguments` alias is for the small fraction that mirror the outer `tools/call` envelope shape; FLAT is what most agents naturally emit when they have a single inner tool to invoke.
- **`pica_execute` returns structured `WRAPPED_ARGS_MISSING` when the wrapped tool needs args.** When the dispatcher resolves no wrapped args AND the wrapped tool's schema declares `required` fields, dispatch is short-circuited with a structured error naming the missing fields and showing both canonical (`args: {…}`) and FLAT example call shapes. Previously the dispatcher silently invoked the wrapped tool with `{}`, surfacing as opaque downstream failures like `PG_INVALID_UUID` on `/admin/works/undefined`. Best-effort — wrapped tools unknown to the registry still dispatch through, letting their own validation speak.
- **`pica_works_inspect` degrades gracefully on missing entities.** Calls naming a non-existent work id no longer fail with an opaque `PG_INVALID_UUID` / 500. The tool returns a structured `{ ok: false, error: "WORK_NOT_FOUND", context: { work_id } }` shape and lists the closest candidates (by name fuzzy match) so the calling agent can recover without round-tripping the user.
### Fixed
- **`pica_export_catalog_csv` schema reconciled with backend.** The tool's `format` enum is now `pro` | `distributor` | `nro` | `sync-pdf` (was `csv` | `json`, both rejected by the backend) and `format` is now marked required. The schema also exposes `work_id` (string) and `work_ids` (string[]) — the backend requires one. Tool description rewritten to explain each format's destination (PRO/CMO, DSP delivery, neighbouring-rights body, sync one-sheet) plus the XOR work scope and the `sync-pdf` single-only constraint. Prior to this fix the tool was uncallable from its own schema: agents got three sequential 400s and could not recover from the surface.
## [2.59.0] - 2026-05-22
Catalog-hygiene release. The interpretation layer an agent had to rebuild from row prose every session — what kind of person record is this, is this work near-complete, why is this attention row at the top — is now exposed as typed columns, queryable filters, sortable fields, and component-scored objects. One breaking change to `pica_dashboard_attention` rows (urgency).
### Added
- **`pica_people_query` filters by record kind and outreach readiness.** New filter params `record_kind` (`individual` / `placeholder` / `artefact`) and `outreach_state` (`ready` / `needs_email` / `needs_disambiguation` / `not_a_target` / `do_not_contact` / `previously_declined`). The same fields ship on every row of the response. "Show me individuals ready to contact" is one call.
- **`pica_works_query` filters and sorts by completeness.** New filter params `completeness_score_lt`, `completeness_score_gt`, `has_statement_data`, and a `sort: completeness_score:asc | completeness_score:desc` option. Every row carries `completeness_score`, `completeness_components`, `completeness_model_version`, and `has_statement_data`. The works-most-worth-fixing query is a single call.
- **Component-scored urgency on `pica_dashboard_attention` rows.** New `urgency: { score, components: { streaming_activity, sync_inquiries, missing_credit_fields, days_to_release }, model_version }` shape replaces the opaque `urgency_score: number`. An agent can now explain why a row is at the top of its queue without re-querying.
- **`failure_mode` field on every attention row.** Stable short identifier (e.g. `missing_iswc`, `no_writer_split`, `placeholder_credit_present`) for client-side clustering. The full set is enumerated in the shared actionable-items source.
- **Four new attention classes**: `placeholder_credits`, `people_records_need_cleanup`, `concatenated_entries`, `publishers_in_people_table`. Surface through `pica_dashboard_attention`, `pica_count_explain`, and `pica_dashboard_briefing` with the same shape as existing classes.
- **Briefing unconnected-people breakdown.** `pica_dashboard_briefing`'s unconnected-people headline now carries `breakdown: { ready, needs_lookup, needs_cleanup, needs_lookup_by_record_kind: { individual, placeholder, artefact } }`. Values sum to the headline by construction.
- **Cached `works.completeness_score`, refreshed on write.** Three new cached columns on the `works` table (`completeness_score`, `completeness_components`, `completeness_model_version`) maintained by an application hook on any cache-invalidating write. The query path on `pica_works_query` reads from cache; the response shape is identical whether served from cache or computed live.
- **Composite indexes for the new query paths.** `works_org_statement_data_idx` on the `work_registrations` satellite (which owns `prs_work_id` / `ppl_recording_id`) and `works_org_completeness_score_idx` on `works(organisation_id, completeness_score DESC)`.
- **`RECIPIENT_DO_NOT_CONTACT` error on `pica_collaborators_invite`.** When the target person has `outreach_state = 'do_not_contact'`, the tool short-circuits before any row is inserted into `collaboration_invites` and returns the new error code with the recipient identifier in `context`.
### Changed
- **BREAKING — `urgency_score: number` removed from `pica_dashboard_attention` row shape.** Callers reading `row.urgency_score` will see `undefined`. Migration: read `row.urgency.score` for the aggregate, or `row.urgency.components.<name>` for the per-axis values.
- **Completeness scorer honours UPC overlay.** `scoreWork()` now resolves UPC through the master-metadata overlay. Works whose canonical UPC lives on an associated `releases` row are no longer scored as if UPC were missing.
- **Completeness scorer recognises non-UK PROs and the MLC.** Beyond the UK PRS/PPL pair, the scorer now counts `pro_ids` membership in ASCAP, BMI, GEMA, SACEM, SOCAN, JASRAC, APRA as registration evidence, and counts `mlc_song_code` presence as US mechanical-rights coverage. ISWC presence counts as registration evidence regardless of UK CMO flags.
- **Completeness response on `pica_works_inspect({ sections: ["completeness"] })` adopts `{ score, components, model_version }` envelope** wrapping the per-category breakdown. Existing readers of `breakdown` keep working.
### Deprecated
- **`suggested_skill` on `pica_dashboard_attention` rows.** Aliased to `drill_down_skill` for one release cycle (~2026-06-22 removal); `pica_count_explain` sources already use that name.
### Fixed
- **`pica_provenance_work` and `pica_provenance_certificate` authenticate correctly under MCP grant tokens.** Both routes (plus four sister provenance routes — certificate download, quality / provenance root / search / verify) now use the standard organisation-context boundary; previously returned 401 to all MCP traffic because they routed through the legacy session-only auth helper.
- **MCP `client_name` attribution lands on every audit row** under both HTTP and stdio transports. The UA-fingerprint regex was widened to recognise `Claude-User` (claude.ai consumer web) and `openai-mcp/X` (ChatGPT) alongside the existing client families, closing a stamping gap on the stateless HTTP transport.
### Database (staging applied; production applied)
- `20260522132950_adr244_record_kind_outreach_state.sql` — `record_kind` and `outreach_state` columns on `people`, with classifier-driven defaults.
- `20260522160011_adr244_c_works_completeness_cache.sql` — cached completeness columns on `works`, composite indexes (`works_org_statement_data_idx` on `work_registrations`, `works_org_completeness_score_idx` on `works`), and a one-shot backfill function.
Consumes `@withpica/mcp-sdk@1.30.0`.
## [2.58.0] - 2026-05-19
### Changed
- **`pica_report_issue` surfaces regression context at submit time.** When the upsert RPC flips a previously-resolved issue back to open (or lands on an already-regressed signature), the tool now narrates "this is a regression of a previously-resolved issue (fixed on `<date>`: `<note>`). Total occurrences now N." and exposes `is_regression` / `previously_resolved_at` / `previous_resolution_note` in `structuredContent.data`. Lets agents tell the user the bug they just hit has a known prior fix without a follow-up `pica_my_reported_issues` call. Backed by a migration that extends `upsert_ops_issue_explicit` and `upsert_ops_issue_from_audit` RETURNING JSONB (applied to staging + production 2026-05-19, backwards-compatible widening). Consumes `@withpica/mcp-sdk@1.29.0`.
## [2.57.0] - 2026-05-18
Closes the ADR-241 substrate sprint — agent-side orientation skill, dashboard coherence with SQL-side `NOT EXISTS`, and 4 sister bug fixes that the agents themselves filed via `pica_report_issue` over the preceding 72 hours.
### Added
- **`first-session` skill (ADR-241).** Agent-side counterpart of `set-up-workspace`. Five phases: identity recognition → reporting-discipline opening note → context load → priority ranking + routing → close-out sweep. Names the right specialist skill per priority class and hands the steering wheel over — does NOT invoke the specialist itself. Discoverable via `pica_skill_list` + the `skill://withpica/first-session/SKILL.md` Resource per the SEP-2640 bridge.
- **`SERVER_INSTRUCTIONS` coaching line.** Directs the agent to call `pica_skill_get("first-session")` on the first user message of a session unless the prompt is already task-specific. Char budget bumped 2300 → 2400 to fit the addition; final length 2337.
- **`pica_issue_agent_grant.scopes.items.enum`** — the 15 `VALID_SCOPES` are now declared inline in the JSON Schema, surfacing through `pica_tool_details` introspection. Closes ops_issue `3a65aa71` (agents trial-and-erroring on `write:works` / `write:credits` / `write:enrichment` which aren't valid; only `write:catalog` is).
- **`pica_dashboard_attention` gains a `class` filter** taking one of `works_without_credits | works_without_registrations | audio_pending | unenriched_writers | recordings_without_splits | disputed_credits`. Specialist skills now have a single-call entry to the top-of-their-own-queue (ADR-242).
- **`pica_count_explain` taxonomy mode** — omit `count_source` to get the full actionable-issue taxonomy as `sources[]` with `drill_down_skill` named per class. Machine-readable routing table that ADR-241's `first-session` skill reads to pick its next step.
### Changed
- **`pica_dashboard_briefing` / `_attention` / `pica_count_explain` are now coherent projections of a single shared source.** Implements ADR-242 § Decision: new `lib/services/dashboard-actionable.ts` (`getActionableIssues()` with a per-class registry); three projection helpers shape its output for the three tool handlers. `sum(count_explain.sources[].count) === briefing.critical_issues_total` for the same org/moment, by construction. Closes ops_issue `4042e806` (briefing 1495, count_explain 338, attention 0 — three tools, three numbers, no path through).
- **`pica_works_query health_filter='needs_attention'` no longer errors.** Hardens `getWorksHealth()` with per-work try/catch; restores `needs_attention` routing in the assembled-truth path. Closes ops_issue `4da6a869`.
- **`pica_split_sheet_generate` "No credits found" surfaces as 400, not 500.** Routes through the user-input-error classifier so agents see an actionable message instead of an opaque server error. Closes ops_issue `899a3d63`.
- **`pica_submit_feedback` dedups by content-hash, not session-time.** Distinct content within the same session now passes; identical resubmissions still dedup within the rate-limit window. SDK errors propagate to `mcp_audit_log` via the error envelope (was silent generic error). Closes ops_issues `4bab716a` + `808e5fa0`.
- **`pica_memory_save` accepts both kebab-case slugs and UUIDs.** Root cause: validator used `apiKey.keyId` (not a valid `user_id` FK) for the `user_id` field; row insert failed silently and surfaced as `"invalid id format"`. Closes ops_issue `315ef479`.
### Dependencies
- `@withpica/mcp-sdk` `^1.27.1` → `^1.28.0` (new `DashboardResource.actionableItems` + `CountExplainResource.taxonomy` methods).
- `@withpica/mcp-utils` `^1.21.1` → `^1.22.0` (workspace-version-alignment fix; develop's Vercel deploy was failing since 2026-05-17 because the `^1.21.1` range that 2.56.1 introduced was not satisfied by the local `1.21.0` workspace, forcing CI/Vercel to pull a duplicate copy from npm registry and breaking type identity).
### Database (staging applied; production applies on develop → main promote)
- Migration `20260517220000_adr242_actionable_issues_rpcs.sql` — 4 Postgres functions (`dashboard_actionable_works_without_credits`, `..._works_without_registrations`, `..._recordings_without_splits`, `..._unenriched_writers`) using SQL-side `NOT EXISTS`. PICA builds for scale; the fetch-all + filter-in-JS pattern was rejected by the founder. All four `LANGUAGE sql STABLE SECURITY INVOKER SET search_path = ''` per the 2026-05-11 function-hardening sweep.
## [2.56.1] - 2026-05-17
### Changed
- Brand consistency: "PICA" → "withPICA" in package metadata, keywords (added `"withpica"`, `"music-catalog"`, `"music-rights"`), README, JSDoc headers, and customer-visible tool descriptions: `pica_delete_my_account`, `pica_gdpr_export_my_data` (description + completion message), `pica_telegram_connect`, `pica_onboarding_complete` (description + `intent_notes` field), and the `new-catalog-setup` prompt.
- Registered `pica_works_attest` and `pica_credits_send_for_attestation` in `REQUIRED_SCHEMAS_SOURCE` companion_calls (both tools declared workflow membership but weren't referenced — pre-existing drift caught by `lint:required-schemas`).
### Fixed
- **`pica_submit_feedback` + `pica_physical_assets_create` no longer leak SDK errors to the user (PRs #453 + #457, 2026-05-15).**
Both executors had naked SDK calls — when the upstream API errored, the
framework's outer catch in `tools/index.ts` JSON-stringified the error
body straight to the conversation (`{error: "UNKNOWN_ERROR", message: "..."}`).
Wrap each SDK call in `try/catch`: on throw, fire-and-forget
`pica.opsIssues.reportIssue({ kind: "tool_failure", ... })` with the
underlying message + status + parameter shape in `context`, return
neutral text (`"couldn't <verb> right now — the team has been alerted."`).
Input-validation early-returns are preserved — those carry actionable
text and aren't server failures. PR #457 added `tool_failure` to the
`OpsIssueExplicitKind` taxonomy and retagged both wrappers (PR #453
shipped as `kind: "data_issue"` because `tool_failure` didn't exist
yet). 12 new unit tests across `feedback.test.ts` (new file) +
`assets.test.ts`. **Note:** the auto-audit upsert path is bypassed
because returning `isError: true` logs as `result_status: "success"`
(`tools/index.ts:1859`) — the executor reports manually instead.
### Added
- **`pica_report_issue` accepts `kind: "tool_failure"` (PR #457, 2026-05-15).**
Sixth value in the agent-explicit kinds enum. Lets agents file SDK /
upstream-tool failures directly via the report-issue tool, mirroring
the new fail-quiet wrappers.
## [2.56.0] - 2026-05-14
### Added
- **`conflict_strategy` arg on `pica_import_execute` (PR #427, promoted to main via PR #428 on 2026-05-13).**
New optional input on the import executor MCP tool. Enum `["error", "skip"]`,
defaults to `"error"` to preserve current bulk-batch behavior. `"skip"` falls
back to per-row insert on Postgres 23505 unique-constraint collisions
(duplicate `tunecode`/`iswc`), so a single duplicate no longer kills the
entire batch — duplicates surface as warnings and the rest of the rows still
land. Backward-compatible (existing callers without `conflict_strategy` get
the default `"error"` behavior). Pairs with the underlying server-side
satellite-write fix that also shipped in PR #427 (PRO-registration fields
routed to `work_registrations` per ADR-125 WS5).
## [2.55.0] - 2026-05-13
### Added
- **`pica_release_notes` tool + `release-notes://withpica/mcp-server/latest` Resource (PR #412, merged to develop 2026-05-13, promoted to main via PR #417).**
Agent-facing surface for platform release notes. Reads `CHANGELOG.md`
embedded at build time via `scripts/build-changelog.ts`. Returns the
last 3 versions by default with optional `since_version` and `sections`
filters. Resource counterpart serves the full CHANGELOG for
Resource-native clients (SEP-2640 bridge pattern, consistent with
ADR-140 Phase 2b). Disambiguated from catalog "what's new" phrasing
by tool name (`release_notes` ≠ "what's new") + NOT-FOR disclaimer
in description pointing catalog phrasing at `pica_dashboard_briefing` /
`pica_works_query`.
## [2.54.0] - 2026-05-13
> Skipped `2.53.0` — that version was claimed by a parallel publish on
> 2026-05-12 before this branch's publish chain ran. Sister of the
> sdk's `1.26.0 → 1.27.0` skip in the same chain. See the
> `feedback_npm_publish_chain_order` memory entry for the pattern.
### Added
- **`pica_import_analyze` / `pica_import_validate` / `pica_import_execute` accept `file_id` as an alternative to `file_content` (PR #415 → develop `2e74411bc`, PR #416 → main `3849c98e4`).**
Closes the upload-then-import loop. After `pica_upload` →
`pica_upload_complete`, agents can pass the resulting `documents.id`
as `file_id` instead of pasting raw CSV content into a tool argument.
Server resolves the bytes via a short-lived signed URL — org-scoped,
CSV MIME-validated, 20 MB cap, UTF-8 validated. Requires
`@withpica/mcp-sdk@^1.27.0` for the matching `ImportFileSource`
type. Backwards-compatible: existing `file_content` callers
unchanged.
### Fixed
- **Stale analyze-route assertions in `app/api/admin/import/__tests__/` flipped 500 → 400 (PR #396 era cleanup, bundled with the bridge).**
Tests had been asserting the pre-PR-#396 500 status for parseCSV
throws; route correctly returns 400 (caller-supplied bad input) since
that fix. Loosened 4 `"fileContent is required"` exact-match
assertions to `/fileContent or fileId/i` to match the new mutually-
exclusive contract.
## [2.52.2] - 2026-05-13
### Changed
- **Server-instructions polish — workspace deeplink hint after writes + paragraph trims (commits `5a2bca4a1`, `7a3e2c226`).**
Added a one-line coaching that agents share `https://withpica.com/workspace?type=<entity>&id=<id>` after every write tool so users get a clickable receipt back to the workspace — the cheapest version of the "chat ↔ workspace" tie-in (ADR-234), no per-tool sweep, agents construct the URL from the entity ID already in every write response. Two trims funded the addition: the `pica_execute` collision disclaimer compressed to one sentence (~120 chars saved) and the peer-MCP paragraph dropped its inline URI-scheme inventory (`gmail://` etc.) in favour of pointing at `docs/mcp-source-uri-conventions.md`, while gaining a load-bearing sentence that citations persist verbatim and propagate to applied entities (ADR-178 — `field_proposals.sources` jsonb + on-apply propagation to `recordings.field_sources`). Token budget restored 2400 → 2300, final length 2192/2300, headroom 108 chars.
## [2.52.1] - 2026-05-13
### Fixed
- **`pica_import_analyze` TypeError (`parsed.rows` → `parsed.preview`) (PR #408, shipped 2026-05-13).**
Every successful analyze call returned `CSV analyze failed: Cannot read
properties of undefined (reading 'slice')`. Three-layer contract drift:
the route returns `parsed: { headers, preview, totalRows, delimiter }`,
the SDK type declared `parsed: { headers, rows, totalRows }` (wrong
field, missing `delimiter`), and the MCP executor read `parsed.rows
.slice(0, 3)`. The SDK's `request<T>()` JSON.parse-cast with no runtime
validation let TypeScript compile the lie. Aligned executor to read
`parsed.preview`; SDK type aligned in `@withpica/mcp-sdk@1.25.1`.
## [2.52.0] - 2026-05-12
Closes out the accumulated Unreleased buffer (some entries below shipped in
2.49.0–2.51.0 but were never moved out of Unreleased; the `lint-changelog`
script that lands in the next bump prevents this drift going forward).
### Added
- **File-delivery tools `pica_file_deliver` + `pica_file_revoke_delivery` (PR #385, shipped 2026-05-12 in `@withpica/mcp-server@2.52.0`).**
Agent-facing counterpart to the upload tools — symmetric with `pica_upload` /
`pica_upload_file`. Wraps the first-class `/admin/files/deliver` surface
(token + recipient_email + expiry + revocation) so agents can share files
out of PICA with audit-trail accountability and a kill switch. Default
expiry 48 hours; max 168 (one week). Registered in `discovery.uploads`
(now bidirectional), `metadata.ts`, `recovery-hints.ts` (deliver →
`rich_response`, revoke → `terminal`), and `lib/billing/tool-cost-model.data.ts`
(deliver → export/EXPORT, revoke → admin/ADMIN). Backed by new
`FilesResource` on `@withpica/mcp-sdk@1.25.0`.
- **ADR-214 follow-up: cross-transport resource parity lint (`scripts/lint-resource-coverage.ts`).**
Asserts that `ResourceRegistry.listResources()` (stdio) and `UI_RESOURCE_DEFS` / the
anonymous-lobby handler (HTTP, `app/api/mcp/route.ts`) expose the same resource URIs.
Catches the W2 drift class — resources added to one transport only — before publish.
Four assertions: stdio-lobby ⊆ HTTP-lobby, HTTP-lobby ⊆ stdio-lobby,
stdio-authenticated \ ADR-152-carve-out ⊆ HTTP-authenticated, and the inverse
HTTP-authenticated ⊆ stdio-authenticated. Wired into `prepublishOnly` (after
`lint:agent-guide`) and `.github/workflows/mcp-tool-lint.yml`.
**Pre-existing drift detected:** `pica://schemas/sync-placement-required` is present
in `UI_RESOURCE_DEFS` (added in 2.47.0 per CHANGELOG) but absent from
`ResourceRegistry.listResources()`. The lint correctly flags this; see the PR for
the fix path.
### Changed
- **`metadata.test.ts` invariants tightened + counts re-baselined (PR #385).**
Four long-standing failures resolved as part of the file-delivery sweep:
(1) stale total-count assertion (175 → 242) reframed as a documented
tripwire — drift means a tool was added without a `TOOL_METADATA` entry;
(2) stale destructive-count assertion (12 → 17) — same shape, security-
relevant because the destructive-tool dispatcher routes on `risk`;
(3) `display_name` lowercase rule was over-strict — relaxed to "first
character lowercase" so legitimate acronyms (S3, GDPR, ISNI, PICA, Azure,
OAuth) pass per `.claude/rules/ui-components.md`'s proper-noun convention;
(4) `pica_storage_disconnect` had `risk: "destructive"` + `retry_safe: true`
in violation of the destructive-tool invariant — flipped `retry_safe` to
`false`. Two-step confirmation token remains the actual safety mechanism;
the tool's data-level idempotence claim stays true.
- **Tool registry catch path now forwards `error_message` to the audit log (PR #308).**
When a tool throws, the registry calls `formatError()` and now passes the
parsed `message` through to `logToolExecution` as `error_message`. Pre-fix,
every error row in `mcp_audit_log` landed with `error_message=null` because
the wire shape stripped the field — even when the wrapper held the
underlying cause. Net effect for diagnostics: rows like the 116 silent
`pica_merge_duplicates` failures on 2026-04-09 (which couldn't be
attributed to any specific cause) will now carry the message going forward.
Sister change in `@withpica/mcp-utils` adds the field to `McpAuditEntry`.
- **`pica_integrations_status` advertisement updated to current tools (PR #308).**
ADR-179 (commit `dc1bfca72`, 2026-04-21) folded the six per-source
`pica_enrich_*` tools into `pica_resolve_work` / `pica_resolve_person` /
`pica_resolve_recording` with a `sources:[...]` parameter, then removed
them. But `mcp-server/src/tools/integrations.ts` continued to advertise
the removed names in the `platformReads` list, the tool description, and
the summary prose. Agents reading the advertisement called names that no
longer existed → UNKNOWN*ERROR loops on every catalog-enrichment ask
routed through MLC/Spotify/MusicBrainz/YouTube/Discogs/ISNI. Now points
at the surviving tools with the correct source value. Two regression
tests in `integrations.test.ts` lock down that no removed
`pica_enrich*\*`tool name appears in the advertised`use_via` strings.
## [2.48.0] - 2026-05-03
### Changed
- **Tool description scrub (PR #188).** Stripped internal-only ADR pointers and
email-vendor names from user-visible tool descriptions across batch A
(agreement-types, agreements, bulk, collaborators, credits), batch B
(app-tools, feedback, projects, enrichment, recordings), and batch C.
Replaced ADR-N references with the concept the ADR documented (e.g.
"ADR-198 round-trip" → "verify the credit persisted") and removed
Postmark from collaborator bulk-invite description and elicitation
warning text. Tool descriptions surface to third-party agents and shouldn't
reveal internal infrastructure choices.
- **recovery_hints expansion (PR #188).** Annotation fix + additional
recovery hints for tools that fan out across sections.
- **`pica_releases_show` best-effort catch markers (ADR-224 convention).** Marked
the two best-effort catches in `releases-rich.show()` (tracks + cover-art
fallbacks) with `/* best-effort */` to match the ADR-224 Phase 4 lint
convention. No behaviour change — the catches were already documented inline;
this just makes the marker greppable so future scope extensions to `_show`
tools recognise the intent. Surface audit confirmed all 9 `_inspect` tools
addressed, 22 `_list` tools clean, no swallow patterns elsewhere.
## [2.47.0] - 2026-05-02
### Added
- **ADR-222 W3 — sync placements MCP tool surface (6 tools).**
- `pica_sync_placements_query` — list placements with filters (work_id, status, verification_status, brand, production_company, person_id, recording_id, source_license_enquiry_id, min_fee, currency).
- `pica_sync_placements_inspect` — fetch one placement with linked recordings, sources, agreement, and parties.
- `pica_sync_placements_create` — atomic create (placement + draft sync_license agreement + ≥1 agreement_party + recordings + evidence sources in a single Postgres transaction). Empty `contacts[]` rejects with structured `MISSING_CONTACT` (`next_tool: "pica_people_query"`). New people auto-created inline via `person_name` instead of `person_id`.
- `pica_sync_placements_update` — patch placement fields. Operator-only when `verification_status='operator_verified'|'disputed'` or `confidentiality_level` changes (throws structured `OPERATOR_REQUIRED`); illegal status transitions throw `INVALID_TRANSITION`.
- `pica_sync_placements_delete` — operator-only hard delete (two-step confirmation token). Customer agents receive structured `OPERATOR_REQUIRED` with `next_tool: "pica_sync_placements_update"` pointing at the soft-delete path (`status: 'terminated'`).
- `pica_sync_placements_cite` — attach an evidence source; auto-recomputes `verification_status` (any non-attestation kind → `evidence_attached`).
All 4 mutating tools wrapped with `withBillingGate`. Stdio path delegates to `pica.syncPlacements.*` (new `@withpica/mcp-sdk` resource). HTTP transport mirrors via the shared `ToolRegistry`. Paired with W4's REST routes at `/api/admin/sync-placements`.
- **`pica://schemas/sync-placement-required` workflow schema resource (ADR-214).** Required + recommended fields for `pica_sync_placements_create` plus 4 companion call entries (inspect / update / cite / delete) and a worked example. Embeds the three `sync_placements` CHECK enums (`status` 5 values, `verification_status` 5 values, `confidentiality_level` 3 values) via the build-time generator + `schema-mirror.json` extension. Lives on both stdio (via the schema generator) and HTTP transport (`UI_RESOURCE_DEFS` in `app/api/mcp/route.ts`).
- **`WorkflowTag` union expanded 15 → 16 values** (added `sync-placement-required`). All 6 sync-placement tools tagged with this workflow; `lint-required-schemas` enforces forward + inverse coverage.
- **`schema-mirror.json` extended with `sync_placements` table** for status / verification_status / confidentiality_level CHECK enum embedding.
- **6 cost-model entries in `lib/billing/tool-cost-model.ts`** (READ for query/inspect, WRITE for create/update/delete/cite). Unblocks ADR-180 lint cost-model coverage check; ADR-215 shadow-metering will record `billable_events` rows on these tools from first call.
- **`sync_placements` discovery category** in `mcp-server/src/tools/discovery.ts` — layered-discovery agents (ChatGPT, Claude.ai connectors with `MCP_LAYERED_DISCOVERY=1`) can find the 6 tools via `pica_discover`.
- **MCP scope mapping** for the 6 tools under existing `read:agreements` (query + inspect) and `write:agreements` (create + update + delete + cite). Bundled under the agreements scope rather than minting a new public scope for one feature — every placement is backed by a sync_license agreement.
- **Four group-membership tools (ADR-223 Phase 5).** Customers can now
manage group → member relationships on people directly via AI:
- `pica_group_add_member` — add a person to a group person row.
Server-side trigger enforces parent.is_group=true and same-org
constraint; tool surfaces friendly errors at the route layer.
Defaults: `role='member'`, `receive_notifications=true` (opt-out
per membership). Wrapped in `withBillingGate`.
- `pica_group_remove_member` — set `left_at` on the active row.
Rows are never deleted; member history is preserved.
- `pica_group_list_members` — active members by default;
`include_inactive=true` for history.
- `pica_person_list_groups` — inverse: every group a person is or
was a member of.
Role enum: `member`, `founder`, `lead`, `touring_member`,
`session`, `guest`, `producer_in_residence`. Free-text `role_label`
for variants outside the enum. Tools route through `mcp-sdk`
`groups` resource → `/admin/groups/*` and
`/admin/people/{id}/groups` HTTP routes.
### Changed
- **`@withpica/mcp-sdk` peer bumped to `^1.20.0`.** Required for the
new `groups` and `syncPlacements` resources; older SDK versions lack
`PicaClient.groups` and `PicaClient.syncPlacements`.
### Notes
- The 4 mutating sync-placement tools (`_create`, `_update`, `_delete`, `_cite`) need ADR-198 holdout contract specs in the sibling `Good-FM/pica-holdout-tests` repo. Coordinator follow-up — Path A from W3 Stage 1 audit §11.E.
- The brief's references to a `mcp-server/src/__tests__/contract-baseline.json` file and a `lint:contract-baseline` script are out of date; both were removed in ADR-180 Phase 3 (the lint chain is now fully strict by default — `scripts/lint-mcp-tools.ts` runs as part of `prepublishOnly`). W3 reinterpreted Stage 4 as ADR-180 metadata + cost-model + taxonomy entries (all added) plus ADR-198 holdout contracts (sibling repo, follow-up).
## [2.46.0] - 2026-05-02
### Changed
- **`pica_update_my_identity` now accepts `full_name`.** Customers can
update the name on their PICA account via AI (e.g., fix typos from
signup). Routes to `user_profiles.full_name`; rolls back via
`revertMyIdentity` if the agent-action stamp insert fails. Tool
description rewritten to lead with name capture. Closes the
edit-symmetry gap surfaced during the SEV-1 sprint: previously the
user could AI-rename their org but not their own name.
- **`pica_update_organisation_profile` now accepts `name`.** Customers can
rename their organisation via AI (e.g., fix signup typos, replace the
default "My Organization" placeholder). Updates `organisations.name`
directly; non-empty TRIM validation matches the DB-layer SEV-1 guard
shipped earlier on 2026-05-01. Tool description updated to mention the
typo-fix use case. Closes the placeholder-name lock-in trap surfaced
during the SEV-1 onboarding-stuck-state cleanup.
## [2.45.0] - 2026-05-01
### Added
- **ADR-214 Phase 3a — 3 new workflow schema resources.**
- `pica://schemas/session-required` (studio session logging — `pica_sessions_create` primary; `pica_sessions_types` companion for session_type_id lookup; `pica_sessions_get` / `_list` for read).
- `pica://schemas/split-sheet-required` (publishing royalty splits on works — `pica_split_sheet_generate` primary; `pica_split_sheet_send` / `_get` / `_list` companions).
- `pica://schemas/recording-splits-required` (master ownership splits on recordings — `pica_recording_splits_create` primary with ADR-200 form-confirmation gate; `pica_recording_splits_verify` / `_list` companions).
Each derives required + recommended fields from live tool input schemas at build time; `recording-splits-required` embeds the `split_type` and `role` CHECK enums (4 + 8 values respectively) from a hand-edited `schema-mirror.json` entry verified against prod project `fwgcmjhlwevdnxgqmkmh`.
- **WorkflowTag union expanded 12 → 15 values** (added `session-required`, `split-sheet-required`, `recording-splits-required`). 11 tools migrated from `"infrastructure"` to their P3a workflow tag (4 sessions + 4 split-sheet + 3 recording-splits).
- **Agent-guide expanded 6 → 14 sections** for authenticated mode:
- 3 new write workflow sections (sessions, split-sheet, recording-splits) with canonical tool sequences and schema-URI footers.
- 1 new identifier-assign narrative section explaining the inline-update pattern (`pica_*_update` accepts identifiers as inline fields including satellite-routed person identifiers — `isni`, `musicbrainz_id`, `wikidata_id`, `discogs_artist_id`, `deezer_artist_id` route to `person_enrichment` automatically; the surface is uniform).
- 4 new read-workflow sections covering find-duplicates → merge, query → inspect → resolve gap-finding chain, lookup-person by industry identifier, and audit catalog completeness via dashboard tools.
Authenticated guide now ~12,750 B (lobby variant unchanged ~855 B).
- **`schema-mirror.json` extended with `recording_splits` table** for `split_type` + `role` CHECK enum embedding. `sessions.status` and `split_sheets.status` documented inline in schema `summary` fields rather than `enum_tables` because they are not DB-CHECK-enforced.
- **2 new MCP prompts.**
- `log-a-session` — guided studio session logging with optional `work_id` argument.
- `set-recording-splits` — guided master ownership split assignment with ADR-200 propose-and-confirm + verify flow; takes optional `recording_id`.
- **`_meta.schemas` dispatcher emit on workflow-tagged tool results.** New `attachSchemaHints` private method on `ToolRegistry` mirrors the existing `attachSessionState` pattern verbatim — same merge semantics, null-safe handling, fail-open behavior. Every tool whose `workflows` field references one or more schema URIs auto-emits `_meta.schemas: ["pica://schemas/<workflow>-required", ...]` on successful results. 47 workflow-tagged tools across 13 files automatically tagged via the dispatcher hook (no per-executor edits). `ToolResult._meta` interface widened to `{ session_state?: SessionState; schemas?: string[]; [key: string]: unknown }`. Cross-transport survival verified — both HTTP (`app/api/mcp/route.ts`) and stdio (`mcp-server/src/server.ts`) preserve `_meta` end-to-end.
- **Cost-model entries for ADR-217 + ADR-218 tools** (`pica_notifications_recent` → READ, `pica_notification_acknowledge` → WRITE, `pica_storage_configure_start` + `pica_storage_disconnect` → ADMIN). Unblocks ADR-180 lint cost-model coverage check; ADR-215 shadow-metering now records `billable_events` rows for these tools.
### Changed
- **11 existing prompts updated to reference ADR-214 schema URIs** where the workflow shape is implicated: `analyze-catalog`, `find-duplicates`, `enrich-metadata`, `verify-works`, `assess-catalog-health`, `audit-credits`, `new-catalog-setup`, `close-the-loop`, `register-my-works`, `prepare-for-sync`, `workspace-autopilot`. Agents invoking a refreshed prompt pick up the relevant `pica://schemas/<workflow>-required` (and `pica://docs/agent-guide` where orientation helps) on first read.
- **`pica_merge_duplicates.entity_type` now accepts both forms.** Previously the enum was singular (`"work" | "person"`) while `pica_find_duplicates` was plural (`"works" | "people"`) — a real footgun documented in ADR-214 P3a Stage 1 R4. Tool now accepts all four (`works` / `people` / `work` / `person`); canonical plural form (`works` / `people`) matches the rest of the catalog tools. Internally coerces to plural for the success message and to singular for the SDK call. Backwards-compatible — no existing caller breaks. Success message no longer renders the broken `work(s)` / `person(s)` artifact when callers pass the plural form. The R4 confirm-branch (two-step ADR-200 confirmation flow) also uses canonical coercion with a try/catch fallback to "entities" so unknown entity_types don't break the confirm UX.
### Fixed
- **`pica_recordings_by_work` drift cleanup.** Removed tool was still referenced in:
- `mcp-server/src/prompts/index.ts` `verify-works` prompt body — swapped to `pica_recordings_query` with `work_id` filter (single-line drift fix).
- `mcp-server/src/tools/recovery-hints.ts` `next_tool` annotation — swapped to `pica_recordings_query`.
- `mcp-server/README.md` Recordings Management section — section refreshed to current 5 tools (`_query`, `_inspect`, `_create`, `_update`, `_delete`); count corrected 6 → 5. Larger README drift in works/people/etc. sections flagged as separate cleanup.
- `@withpica/mcp-utils` `recovery-hints.ts` DUPLICATE_ISRC `next_tool` — same swap (mcp-utils@1.15.1).
- **Source ↔ dist invariant restored** for `prompts/index.{js,d.ts,*.map}` (PR #173 source landed without committed dist regen — develop's dist had been missing `log-a-session` + `set-recording-splits`) and `tools/recovery-hints.{js,*.map}` (source was correct; dist was stale). No semantic change beyond what was already on develop; clears phantom local-tsc errors per `feedback_root_tsc_stale_mcp_dist`.
### Dependencies
- `@withpica/mcp-utils` `^1.14.0` → `^1.15.1` (recovery-hints `next_tool` fix).
## [2.44.0] - 2026-04-30
### Added
- **ADR-214 Phase 2 — 6 new workflow schema resources.**
- `pica://schemas/audio-upload-required` (3-step chain: presigned →
complete → analyze + poll)
- `pica://schemas/enrichment-resolve-required` (resolve fan-out →
proposals_list → proposal_apply / reject)
- `pica://schemas/agreement-required`
- `pica://schemas/multimedia-required`
- `pica://schemas/export-required`
- `pica://schemas/claim-required`
Each derives required + recommended fields from the live tool input
schemas at build time and embeds CHECK enums from `schema-mirror.json`.
Agent-guide expanded with 6 workflow sections (4 net-new, 2 rewrites
with schema-URI footers); intro bumps "five core" → "eleven core" and
"~227 tools" → "~231 tools".
- **WorkflowTag union expanded to 12 values** (5 Phase 1 + 6 Phase 2 +
`infrastructure`). 28 tools migrated from `"infrastructure"` to their
Phase 2 workflow tag (4 audio-upload, 6 enrichment-resolve, 4
agreement, 4 multimedia, 5 export, 5 claim).
- **`schema-mirror.json` extended with 6 new tables** for Phase 2
CHECK-constraint enum resolution (agreements, discovered_artists,
discovered_credits, discovered_custody, enrichment_proposals,
multimedia_items). Verified live against prod project
`fwgcmjhlwevdnxgqmkmh`.
- **HTTP transport coverage** — `app/api/mcp/route.ts` `UI_RESOURCE_DEFS`
- `UI_RESOURCE_BODIES` extended with the 6 new schema URIs (matches
the Phase 1 pattern from PR #152). Per
`feedback_cross_transport_resource_coverage`.
## [2.42.0] - 2026-04-30
### Added
- **ADR-218 Primitive B — `pica_notifications_recent` tool.** New read-only
MCP tool (`risk: "safe"`, `retry_safe: true`) returning the cross-transport
`discovery_events` feed for the calling org, ordered by `created_at` DESC
with optional `unread_only: boolean` filter (per-surface dedup against
`seen_in`). Default limit 20, max 100. Distinct from `pica_notifications_list`
which targets the person-scoped `pica.notifications` system; this reads the
org-wide ledger that web bell + telegram dispatcher also share.
- **ADR-218 Primitive C — `pica_notification_acknowledge` tool.** New
mutating tool (`risk: "mutating"`, `retry_safe: true`) that appends `'mcp'`
to each event's `seen_in` array. Idempotent — re-acknowledging is a no-op.
Org-scoped server-side: out-of-org ids are silently skipped. Empty
`event_ids` returns a `VALIDATION_ERROR` without hitting the API.
- \*\*Both tools registered in the existing `notifications` taxonomy bucket
- TOOL*METADATA (`category: "comms"`)\*\* + tagged `workflows: "infrastructure"`
per ADR-214 W4. Ride alongside the legacy `pica_notifications*_`and`pica*notify*_` families — the agent disambiguates from the descriptions.
### Changed
- **ADR-218 Primitive D — telegram dispatch dedup.** `telegramNotificationService.notifyEvent`
now consults `discovery_events` for an existing undismissed row matching
`(organisation_id, event_type, entity_id)` where `'mcp' = ANY(seen_in)`.
When found, suppresses the entire dispatch — closes the re-nag loop for
`until_resolved`-strategy alerts. Fresh `one_shot` events with no prior
row still fire normally. Lookup is fail-soft: any DB error logs a debug
and proceeds with dispatch (better an extra ping than a silently-dropped
alert).
- **ADR-218 Primitive A — `_meta.session_state` envelope gains optional
`notifications` field.** `fetchSessionCounts` propagates the cross-transport
unread summary from the catalog-stats response into the envelope. Missing
field on older API servers leaves the envelope key off (no fabricated zero).
### Bumped
- `@withpica/mcp-sdk` peer to `^1.18.0` (`DiscoveriesResource.recent` +
`.acknowledge` methods + `NotificationsSummary` type on `CatalogStats`).
- `@withpica/mcp-utils` peer to `^1.14.0` (optional `notifications` field on
`SessionStateCounts` + exported `NotificationsSummary` type).
## [2.41.0] - 2026-04-30
### Added
- **ADR-210 P3 — `getAuthenticatedInstructions(slice)` + `getTrialEndingClause(slice)`
exports in `src/server-instructions.ts`.** New helpers that optionally append
a state-conditional trial-ending nudge to the authenticated handshake
instructions. The clause fires only when `billing_state === 'trial'` AND
`trial_days_remaining` is non-null AND `<= 3`; in all other cases the
function returns the unchanged base const. Pass `null` when no slice is
available — slice-less callers fail safe. Existing
`SERVER_INSTRUCTIONS_AUTHENTICATED` const is unchanged (parity with
`lib/mcp/server-instructions.ts` preserved).
- **New `TrialNudgeSlice` interface export.** Minimal shape consumed by
the helper: `{ billing_state: string; trial_days_remaining: number | null }`.
Structurally compatible with the platform's `OrgBillingSlice` type so
callers can pass either.
### Note on stdio behaviour
The stdio binary in this release continues to call the equivalent of
`getAuthenticatedInstructions(null)` at boot (returns the unchanged const) —
trial state is not resolvable at boot without a `pica.catalogStats()` round
trip. Stdio agents still see live `trial_days_remaining` via
`_meta.session_state` on every write-tool result. A future phase may add
boot-time slice resolution; this release ships the helper itself so the
HTTP transport (`/api/mcp`) can use it immediately on a per-request basis.
## [2.40.0] - 2026-04-30
### Changed
- **ADR-214 W4 — `workflows` field is now required on `ToolDefinition`.**
Flips the optional `workflows?: WorkflowTag | WorkflowTag[]` field
introduced in W1 to a required `workflows: WorkflowTag | WorkflowTag[]`.
TypeScript compilation now enforces that every new tool authored
declares its workflow membership at the source site — no escape
hatch except the explicit `"infrastructure"` opt-out. Drift
prevention mechanism #1 from ADR-214 §"Drift prevention" is now
fully load-bearing. `lint-required-schemas.ts` hardened: the
parallel-window warning for untagged tools becomes a hard error
(defence-in-depth in case a tool somehow reaches the registry
without a `workflows` field at runtime).
### Added
- **ADR-214 W2 — agent-guide + required-fields schema resources.**
Two new resource families on the catalog `mcp-server`:
- `pica://docs/agent-guide` — workflow narrative for agents. Visible
in both lobby and authenticated modes; content branches on
`lobbyMode`. Lobby variant (~865 bytes) covers the signup → magic
link flow + what unlocks. Authenticated variant (~4.3 KB) lists the
five core workflows (register a work, add a recording, add audio,
add recording credits, enrich metadata) with verbatim tool sequences
and 8 common gotchas — verified against the live tool registry on
2026-04-30.
- `pica://schemas/work-required`, `pica://schemas/recording-required`,
`pica://schemas/work-credits-required`,
`pica://schemas/recording-credits-required`,
`pica://schemas/person-required` — JSON contracts derived at build
time from each tool's `inputSchema` (in `ToolRegistry`) joined with
`schema-mirror.json` for live CHECK constraint enums. Authenticated
mode only.
- **Build-time generator + schema mirror.**
- `mcp-server/scripts/build-required-schemas.ts` — runs as part of
`npm run prebuild` (chained after `bundle-apps.ts`); reads
`required-schemas.source.ts` + `ToolRegistry` + `schema-mirror.json`
and emits the deterministic `required-schemas.generated.ts`
(committed to git so PR diffs surface drift, per
`feedback_type_regen_cumulative_drift`).
- `mcp-server/src/resources/schema-mirror.json` — committed snapshot
of CHECK constraint enums for `works`, `recordings`, `work_credits`,
`recording_credits`, `audio_files`. Refresh via
`mcp-server/scripts/refresh-schema-mirror.ts` (humans only; CI never
auto-regenerates).
- **Inverse-coverage CI lint (`scripts/lint-required-schemas.ts`).**
Walks `ToolRegistry` and `REQUIRED_SCHEMAS_SOURCE` from both
directions: every tool whose `workflows` field tags it for a workflow
must appear in the source file; every source reference must resolve
to a real tool. During the parallel-development window (W1 still in
flight), tools missing the `workflows` field emit a single summary
warning (per coordinator decision Stage 1 §R2). Once W4 makes the
field required, the warning becomes an error.
- **Agent-guide drift lint (`scripts/lint-agent-guide.ts`).** Greps
every `pica_*` token in `agent-guide.ts` against `ToolRegistry` (in
both `discoveryMode` settings, so meta-tools like `pica_discover` are
resolvable). Catches stale references after tool renames.
- **Meta-tests** for both lints (per `feedback_meta_test_load_bearing`):
3 / 3 regressions caught and reverted before ship — see
`docs/follow-ups/2026-04-30-adr214-w2-meta-test-outcomes.md`.
### Changed
- `mcp-server/package.json` — `prebuild` now chains
`bundle-apps.ts && build-required-schemas.ts`. New scripts
`lint:required-schemas` and `lint:agent-guide` added; both wired into
`prepublishOnly` after the existing checks.
- `mcp-server/src/resources/index.ts` — registers 6 new URIs across
`listResources()` (1 in the lobby branch, 5 in the authenticated
branch) and `readResource()` (1 case for the agent-guide variant
branch, 5 cases for the schema URIs).
### Dependencies
- `pg` + `@types/pg` added as devDependencies (used only by
`refresh-schema-mirror.ts` for direct Postgres CHECK-constraint pulls
against staging).
### Internal
- **ADR-214 W1 — `WorkflowTag` type + optional `workflows` field on tool
definitions.** Adds a union type
(`"work-required" | "recording-required" | "work-credits-required"
| "recording-credits-required" | "person-required" | "infrastructure"`)
and a `workflows?: WorkflowTag | WorkflowTag[]` field to
`ToolDefinition` at `mcp-server/src/tools/index.ts`. All 231 tools now
carry the field — 8 catalog-core tools (`pica_works_create`,
`pica_works_update`, `pica_recordings_create`,
`pica_recordings_update`, `pica_people_create`, `pica_people_update`,
`pica_credits_update`, `pica_recording_credits_update`) reference one
or more Phase 1 workflows; the remaining 223 carry
`"infrastructure"`. Field is optional in W1 to keep the rollout
additive; W4 (post-merge of W1 + W2 resource scaffolding) flips it to
required so every future tool is forced to declare its workflow at
compile time. No runtime behaviour change in this release.
## [2.39.0] - 2026-04-30
### Added
- **ADR-211 Phase 1 — `pica_signup_start` unauthenticated tool.** New
agent-discoverable signup verb available without a connection key on
both stdio (lobby mode) and HTTP `/api/mcp` (anonymous mode). Mints
nothing locally — every invocation calls
`POST /api/public/onboarding/signup-start` so the
`ONBOARDING_TOKEN_SECRET` signing secret stays on the backend, never
in customer-distributed binaries. Returns a 15-minute JWT-bound
`/onboard/<token>` URL via `resource_link` (universal floor) +
capability-gated `elicitation/create url` (mode: "url"). Constructor
takes `{apiUrl, transport, clientId?}` — stdio binary stamps
`transport: "stdio"`, the HTTP carve-out stamps `transport: "http"`
and threads through any DCR `client_id` for OAuth resume.
- **`SERVER_INSTRUCTIONS_UNAUTHENTICATED` lobby variant.** Short
~600-char handshake text pointing the agent at the 3 lobby tools
(`pica_signup_start`, `pica_sign_in`, `pica_sign_out`) instead of the
full 200+ catalog surface. Selected at boot when `lobbyMode === true`
(stdio binary without `PICA_API_KEY`) and at handshake when
`/api/mcp` is invoked without a Bearer token. Catalog-tool guidance
omitted because no catalog tools are reachable in lobby mode.
- **`SERVER_INSTRUCTIONS_AUTHENTICATED` alias** of the legacy
`SERVER_INSTRUCTIONS` export — the unaliased symbol stays exported
unchanged for backward compatibility.
### Changed
- **Lobby surface is now THREE tools** (was two): `pica_signup_start`
joins the existing `pica_sign_in` + `pica_sign_out` pair. Additive,
not replacement — Phase 1 evaluates consolidation in Phase 2 once
the `/onboard/<token>` page is live and the JWT-bound flow is the
preferred path. ADR-211 § Primitive A editorial amendment 2026-04-30
reflects this.
### Notes
- **`ONBOARDING_TOKEN_SECRET` env var required** on backend
environments (preview + prod already provisioned 2026-04-30). The
`lib/auth/scoped-jwt.ts` substrate gained an additive
`options.secretEnvVar` parameter so each scoped-JWT consumer
(upload-token, onboarding-token, future ADR-214 storage-config) can
rotate its signing secret independently. SUPABASE_JWT_SECRET still
serves as the local-dev fallback in both modes.
- **No new exports in `@withpica/mcp-utils` or `@withpica/mcp-sdk`** —
Phase 1 consumes existing exports (`clientSupportsUrlElicitation`,
`CallerContext`) only. Per
`feedback_workspace_package_publish_discipline.md`: no bumps needed
upstream of this release.
## [2.38.0] - 2026-04-30
### Added
- **ADR-208 Phase 3 Primitive D — `pica_release_show` rich-render tool.**
The catalog's first MCP tool returning `image` + `text` content blocks
in a single agent turn. Cover art ships as a base64 JPEG ≤512px;
markdown summary covers title, year, primary artists, label name, UPC,
catalog number, and tracklist with ISRCs and durations. Read-only
(`readOnlyHint: true`). Registered in
`mcp-server/src/tools/discovery.ts` `CATEGORIES.releases.tools` in
the same commit as the `ToolRegistry` registration per the layered-
discovery completeness rule.
- **`_meta.ui.resourceUri = "ui://pica/release"`** — MCP Apps clients
(Claude Desktop, Cursor) render the iframe via the new resource at
`mcp-server/src/apps/release.ts`. Phase 3 ships the resource stub
(title + markdown + cover image binding); full interactive iframe
with track-list expand and inline streaming embeds is queued under
the ADR-200 Phase 1.x backlog.
- **`structuredContent`** envelope — `{ title, markdown, cover_base64,
cover_mime_type, resource_link_uri }`. Apps clients prefer this over
re-decoding the image content block; non-Apps clients ignore it.
### Notes
- **Universal floor is `image + text` only in Phase 3.** A `resource_link`
block is intentionally omitted because no release UI page exists in
the codebase yet (verified — neither `/portal/releases/[id]` nor
`/admin/releases/[id]`). Building one is queued under
`docs/follow-ups/2026-04-30-adr208-phase3-5-release-detail-page.md`
and ADR-208 § Primitive D was editorially amended on develop
(`30ceefc9d`) to reflect the deferral.
- **"Key credits" deferred from the markdown body.** The existing
`releasesService.listTracks()` join surfaces work + recording artist
but not `work_credits`; adding the join widens Phase 3 scope. Will
land alongside ADR-213 when credit-attribution surfaces consume the
data cheaply.
- **Sharp lives in the main Next.js app, not in this stdio binary.**
Cover-art bytes come from the new `/api/admin/releases/[id]/cover-art-thumbnail`
endpoint that does the resize + cache server-side, so the
customer-distributed `@withpica/mcp-server` install stays free of
sharp's ~50MB platform-specific native binaries.
### Changed
- **Bumps `@withpica/mcp-sdk` peer to ^1.17.0** to consume the new
`ReleasesResource.getCoverArtThumbnail()` method.
`@withpica/mcp-utils` stays at ^1.12.0 (T2.4's bump absorbed via the rebase).
## [2.37.0] - 2026-04-30
### Added
- **ADR-210 Phase 2 — `pica_subscription_status` and `pica_subscription_manage`.**
Two new tools shipping the explicit billing read + action surface
that complements Phase 1's ambient `_meta.session_state.billing_slice`.
- `pica_subscription_status` (`readOnlyHint: true`) wraps the
existing `GET /api/admin/subscription` route and flattens it into
the 9-field shape ADR-210 § Phase 2 specifies (`billing_state`,
`trial_days_remaining`, `trial_ends_at`, `current_tier`,
`capacity_used`, `capacity_limit`, `capacity_pct`,
`recommended_tier`, `summary`). The `summary` line is a
server-side template the agent uses verbatim — predictable
wording, less variance across connectors.
- `pica_subscription_manage` (`readOnlyHint: false`) is a single-verb
dispatcher over the existing `POST /api/admin/subscription`
(subscribe / upgrade) and `POST /api/admin/subscription/portal`
(manage / cancel) routes. Returns `{url, expires_at, surface}`.
Subscribe / upgrade without `tier` returns a recoverable error
with `next_tool: pica_subscription_status` so the agent can
chain to read `recommended_tier` first rather than auto-pick.
Universal-floor `resource_link` content block ships on every
success response per ADR-200 Phase 1; `elicitation/create url`
fires only when `clientSupportsUrlElicitation(server)` (the
capability gate is mandatory because `elicitInput` with
`mode: "url"` throws synchronously against non-supporters per
`feedback_mcp_sdk_capability_gates_elicitation.md`).
- **`subscription` category in `discovery.ts:CATEGORIES`** ("view your
billing state, manage your subscription, and pick a tier"). Lint
enforced by `scripts/lint-mcp-tools.ts` per
`feedback_mcp_taxonomy_completeness.md`.
### Changed
- **Bumps `@withpica/mcp-sdk` peer to ^1.16.0** to consume the new
`SubscriptionResource`. `@withpica/mcp-utils` stays at ^1.12.0
(T2.4's bump absorbed via the rebase).
## [2.36.0] - 2026-04-30
### Added
- **ADR-213 Primitive A — `pica_recordings_create` post-create attribution hints.**
Every successful create call now appends up to three Important-severity
hints via the new `buildRecordingAttributionHints(recording)` helper
(`mcp-server/src/tools/recording-attribution-hints.ts`). Mirror of
`buildCustodyHints` for works:
- `recording_ownership_unset` — fires when `ownership_percentage IS NULL`.
Suggests `pica_recordings_update ownership_percentage:N`.
- `recording_no_credits` — always fires (we never know credits from a
single fresh row). Suggests the new `pica_recording_credits_update`.
- `recording_master_unclaimed` — fires when `ownership_percentage IS NULL`.
Alternative path for multi-party master claims via `pica_custody_claim`.
NULL-only triggers: `0` is "explicit zero", no hint. Negative-path
covered by jest at `recording-attribution-hints.test.ts` per
`feedback_meta_test_load_bearing.md`.
- **ADR-213 Primitive B — `pica_recording_credits_update`.** First-party
recording-side write tool; closes the work/recording credit-write
asymmetry. Writes to `recording_credits` (NOT the trade-secret
enrichment-cascade-fed `credits` table — see `.claude/rules/ip-protection.md`).
Uses the 16-role `RECORDING_CREDIT_ROLES` enum (`MainArtist`,
`FeaturedArtist`, `Producer`, `Mixer`, `Engineer`, ... `Other`).
Songwriting credits (Writer, Composer, Lyricist, Arranger as a publishing
role) continue to route through `pica_credits_update` on the parent work.
Registered in `discovery.ts` `CATEGORIES.credits` in the same commit per
`feedback_mcp_taxonomy_completeness.md`. Listed under `write:catalog` scope
in `lib/services/mcp-scopes.ts`. `nextSteps` points at
`pica_recordings_inspect sections:["recording_credits"]` for ADR-198
round-trip verification.
- **ADR-213 Primitive A.1 — `pica_recordings_inspect recording_credits`
section.** Additive (not replacement). The existing `credits` section
(work-side credits via parent work) keeps its current behaviour. New
`recording_credits` section reads first-party operator-authored credits
directly from the `recording_credits` table via the new
`pica.recordingCredits.list()` SDK method.
- **`TOOL_METADATA` entry for `pica_recording_credits_update`** in
`mcp-server/src/tools/metadata.ts` (catalog/mutating/non-retry-safe).
### Changed
- **Bumps `@withpica/mcp-sdk` peer to ^1.15.0** and `@withpica/mcp-utils` to
^1.12.0 to consume the new `RecordingCreditsResource` and the metadata
entry shipped alongside ADR-213.
- **Tool count moves from 224 → 225** with the addition of
`pica_recording_credits_update`.
## [2.35.0] - 2026-04-29
### Added
- **ADR-208 Phase 2 — `pica_upload` returns a JWT-bound `/upload/<token>` URL.**
The executor now calls `pica.uploads.createSession()` for server-side
mint of a 600s-TTL HS256 JWT scoped to the caller's org / user /
bucket-key / acceptFilter. Universal floor (`resource_link`) and
capability-gated `elicitation/create url` shapes unchanged — only the
URL flips from `${webBase}/workspace` to `${webBase}/upload/<jwt>`.
Tool description updated; obsolete Phase 1.5 forward-reference removed.
### Changed
- **`session_state.pending_uploads` now reads from
`organisations.pending_uploads_count`** (atomic +1 on JWT mint via
`/api/admin/uploads/sessions`, atomic -1 on JWT-path completion via
`/api/upload/[token]`). Phase 1's stub at `0` is gone. Atomic semantics
via the `inc_pending_uploads` SECURITY DEFINER RPC; `GREATEST(0, ...)`
clamp prevents negative drift. Sits alongside T1.2's billing fields on
the same `_meta.session_state` envelope — no shared edit to
`mcp-server-shared/mcp-utils/src/session-state.ts`.
### Notes
- Fail-soft: if `createSession()` throws (network blip, route 5xx), the
executor falls back to the legacy workspace URL and surfaces the error
in the `text_summary`. The card still works for MCP Apps clients via
`_meta.ui.resourceUri` regardless.
### Bumped
- `@withpica/mcp-sdk` dependency range bumped to `^1.14.0`
(`UploadsResource`, `CreateUploadSession*` types,
`CatalogStats.pending_uploads_count` now consumed).
- `@withpica/mcp-utils` dependency range bumped to `^1.11.0`
(T1.2's billing-fields extension on `SessionStateCounts`).
- `mcp-server/server.json` registry manifest version bumped to `2.35.0`
in lockstep with the npm package.
## [2.34.0] - 2026-04-29
### Added
- **ADR-210 Phase 1 — billing slice on `_meta.session_state`.** The
`fetchSessionCounts` private in `ToolRegistry` now resolves the four
billing fields (`billing_state`, `trial_days_remaining`, `current_tier`,
`capacity_pct`) from `pica.catalogStats().billing_slice` alongside the
existing counts. `attachSessionState` flows them onto every successful
write-tool result through the same `_meta.session_state` envelope (no
new emission path — extends the existing one) so the agent sees ambient
billing posture between mutations without a `pica_subscription_status`
round-trip. Phase 2 will land that explicit read tool; Phase 1 ships
the substrate the tool reads from.
- Same fail-soft posture as the existing fetcher: when `this.pica` is
missing (HTTP-discovery without bearer scope), or when the SDK response
is missing `billing_slice` (older server pre-Phase-1), the fetcher
emits a conservative `{ billing_state: "trial", trial_days_remaining:
null, current_tier: null, capacity_pct: 0 }` rather than fabricating
"active" — the agent never sees misleading "active" billing for an
uninitialized context.
### Bumped
- `@withpica/mcp-sdk` dependency range bumped to `^1.13.0`
(`CatalogStats.billing_slice` field now consumed).
- `@withpica/mcp-utils` dependency range bumped to `^1.11.0`
(`SessionStateCounts` shape extended with `BillingSlice` fields).
- `mcp-server/server.json` registry manifest version bumped to `2.34.0`
in lockstep with the npm package.
## [2.33.0] - 2026-04-29
### Added
- **ADR-208 Primitive B — `_meta.session_state` attached to write-tool results.**
The central `ToolRegistry.executeTool` wrapper now composes the six-key
session-state envelope (`works_count`, `recordings_count`,
`completeness_pct`, `mutations_this_session`, `since_last_briefing`,
`pending_uploads`) for every tool whose risk is `mutating` or
`destructive` (`readOnlyHint: false`), and attaches it under
`result._meta.session_state`. Read tools (`risk: safe`) skip this
entirely so the high-volume read path stays cheap; failed mutations
(`isError: true`) also skip because catalog state didn't change.
`pica_dashboard_briefing` running successfully resets the
`since_last_briefing` counter — handled in the wrapper, not the tool.
Cache key is org-scoped via `config.picaApiKey` (stdio) or
`callerContext.callerIdentity` (HTTP); two orgs sharing a worker
process never share state. Fail-open: any error in session-state
composition is swallowed and the result returns without `_meta.session_state`.
See `@withpica/mcp-utils@1.10.0` for the underlying `buildSessionState`
helper and the load-bearing cross-org isolation regression test.
### Changed
- Bump `@withpica/mcp-utils` dep range to `^1.10.0` to pick up the
`buildSessionState` / `incrementSessionMutations` /
`resetSinceLastBriefing` exports that compose Primitive B.
## [2.32.0] - 2026-04-28
### Changed
- Bump `@withpica/mcp-utils` dep range to `^1.9.0`. The previously-pinned `^1.8.0`
resolved (via the npm registry) to a tarball that pre-dated the addition of
`clientSupportsUrlElicitation` and `elicitBulkConfirmation` to the source — Vercel
builds where the lockfile resolved from registry instead of the local workspace
link failed with `TS2305`. 1.9.0 republishes the same source state with both
exports present, so registry-pinned lockfiles now build cleanly without needing
the workspace-link override.
## [2.31.0] - 2026-04-28
### Added
- **ADR-200 Phase 1 — `ToolExecutorContext` plumbed through the registry.**
`ToolExecutor` now takes an optional second arg `(args, ctx?)` carrying the
per-request `Server` reference. `server.ts` passes `{ server }` through to
`executeTool`; the discovery dispatcher (`pica_execute`) forwards it to its
delegate. Existing executors with the legacy `(args)` signature keep working
unchanged — only tools that need `server.elicitInput()` opt into the second
arg. Pre-existing destructive-confirmation gate (two-step token) is
preserved.
- **`pica_upload` portable across non-MCP-Apps clients.** Response now ships
with a `resource_link` content block (universal MCP floor) alongside the
legacy text envelope and `_meta.ui.resourceUri` — Apps clients render the
iframe unchanged, ChatGPT / Gemini / Claude Code / curl-style clients see a
clickable link to the workspace upload page. Clients that advertised
`capabilities.elicitation.url` additionally receive a server-issued
`elicitation/create url` consent prompt; the call is gated on the new
`clientSupportsUrlElicitation` helper from `@withpica/mcp-utils@1.8.0` so
callers without the capability never see the SDK's synchronous
"Client does not support url elicitation" throw. `elicitationId` is a
per-call UUID. Universal floor remains usable when the user declines or the
elicitation times out — the resource_link in the response is the fallback.
- **`pica_download` ships a `resource_link` for the signed file URL.** Same
universal-floor improvement on the download surface — text envelope still
carries the JSON shape ADR-152 cards expect, plus the new resource_link
exposes the URL/filename/mime to clients that render link blocks.
- **ADR-200 Phase 1 — form-confirmation gate on the 4 customer-MCP Class D
bulk tools** (`pica_works_bulk_update`, `pica_people_bulk_update_roles`,
`pica_recording_splits_create`, `pica_collaborators_invite_bulk`). Each
now calls `elicitBulkConfirmation` from `@withpica/mcp-utils@1.8.0`
after the validation + dry_run paths but before persisting. The gate
fires only when the connected client advertises
`capabilities.elicitation`, and in that case the destructive write only
proceeds if the form returns `accept` with `confirm: true`. Universal
floor: clients without elicitation continue to execute as before
(existing destructive-token gate, where applicable, remains
authoritative). 11 new tests cover decline, accept-without-confirm,
no-caps fallback, and dry_run-skips-elicitation across the four tools.
`team_comms_send` (5th Class D tool) is gated in
`@withpica/team-mcp-server@1.3.0`.
- **`ToolResult.content` widened to permit `resource_link` blocks.** Legacy
`Array<{ type, text }>` shape kept compatible via a structural union; tools
that only return text need no changes.
### Changed
- **Tool descriptions for `pica_upload` / `pica_download` updated** to point
at the ADR-200 fallback ladder rather than the prose "tell the user to use
the workspace dashboard" hint that ADR-152 originally shipped. Behaviour
for MCP Apps clients is unchanged.
### Removed
- **9 nested `withCreditGate(...)` wrappers unwrapped** across `tools/audio-files.ts` (2), `tools/exports.ts` (3), and `tools/enrichment.ts` (4). Each always ran inside a `withBillingGate(...)`; the inner wrapper was a no-op shim retained since ADR-162 disabled credit gating. Unwrapping leaves the subscription check as the sole gate on these tools, which matches the billing model.
- **`tests/utils/credit-gate-mpp.test.ts` + `credit-gate-mpp-paths.test.ts` deleted.** Both suites exercised a disabled MPP + pay-per-credit path that no longer has any production call-sites. `tests/tools/metadata.test.ts` also drops its `credit-gated tools should have credits_required set` assertion — redundant now that `credits_required` is gone from the shared `@withpica/mcp-utils` type (companion entry in that package's CHANGELOG). `tests/tools/credits.test.ts` stale `describe("pica_credits_balance")` block (and associated `mockCreditsBalance` setup) removed — that tool was pulled from the surface 2026-04-22 but the tests were missed in that pass.
- **`src/__mocks__/mppx-server.ts` + `mppx-mcp-sdk-server.ts` deleted** and the matching `moduleNameMapper` entries removed from `jest.config.js`. Both mocks existed only to stub out `mppx` for the now-deleted MPP unit tests. The `testPathIgnorePatterns` entry pointing at `credit-gate-mpp-paths.test.ts` is also removed (ghost reference to the deleted file).
- **"credit-gated" prose stripped from tool descriptions** in `tools/app-tools.ts` (`pica_car_preview` description + carPreview reuse comment) and `tools/documents.ts` (`pica_documents_analyse` description). These strings were advertised in the tool description, telling agents a tool was "credit-gated (5 credits)" when it's actually subscription-gated at workspace level.
- **`mppx` runtime dep still listed in `package.json`** — left in place to avoid lockfile churn; no `src/` consumer remains after this commit. Next publisher should drop it and rerun `npm install` to clean the lockfile.
## [2.30.0] - 2026-04-27
### Added
- **`pica_audio_inspect` basic section surfaces 4 previously-invisible
columns** — `recording_id`, `stem_label`, `version_label`, and
`classification`. All four are real columns on `audio_files` written by
`pica_audio_complete_upload`. The 5th audit-flagged field
(`content_type`) is mapped to `audio_format` at write time and was
already surfaced. Closes a Phase 2 gap from the 2026-04-27 read-write
parity audit. SDK type `AudioFile` extended with the same three
optional fields (classification was already there).
- **`pica_organisation_profile` includes `org_type`** — paired read tool's
underlying `/admin/settings/organisation-profile` SELECT now returns
the CHECK-enum value that `pica_update_organisation_profile` already
accepted (publisher / label / pro / sync_library / client / venue /
distributor / other). Same class as the settings/identity fan-out
fixed in 2.29.0 — write side accepted, read side truncated.
- **`pica_physical_assets_inspect` adds a `links` section** — surfaces
the typed-edge rows that `pica_physical_assets_link_work` and
`pica_physical_assets_link_recording` write to `work_production_assets`
and `recording_production_assets`. Pre-fix the link rows were
write-only from the MCP surface even though both
`/admin/assets/[id]/{work,recording}-links` GET endpoints already
existed. SDK gains `assets.listWorkLinks(id)` and
`assets.listRecordingLinks(id)`.
- **`dry_run` flag on three more Class D mutating tools** —
`pica_recording_splits_create`, `pica_collaborators_invite_bulk`,
and `team_comms_send`. Completes the Class D coverage started in the
ADR-199 5a part 1 commit (`a10006e5b`). Each tool early-returns
`{ dry_run: true, would_affect, sample / target_count }` after
validation and before the mutating SDK call, so agents can preview
bulk effects and external-comms dispatch before committing. For
`team_comms_send`, the preview echoes recipient and length-only
metadata; the message body is not surfaced to keep ops messaging
off the dry-run trace.
### Fixed
- **`pica_recordings_inspect` no longer hallucinates into the void on 404.**
ADR-198 verification 2026-04-26 found the inspect tool was returning 200
with `recording_error: "API request failed: 404 ..."` for every missing
entity / missing route handler. Agents read this as a successful empty
result and proceeded to call downstream sections (credits, audio) that
also 404'd silently — no Sentry alert, no audit row, no user-visible
error. Fix: basic-section catch now re-throws `EntityNotFoundError`
(new typed export from `@withpica/mcp-sdk` 1.12.0+), so 404 surfaces
as a hard `isError: true` envelope. Non-404 errors on the basic fetch
still get absorbed into `recording_error` (preserves the partial-result
pattern for transient failures).
## [2.29.0] - 2026-04-25
### Fixed
- **94 previously-undiscoverable tools retrofitted into the layered-discovery
taxonomy.** Same bug class that bit four times in three weeks
(`pica_upload`/`pica_download` since ADR-152, `pica_submit_feedback` since
ADR-184, `pica_report_issue`/`pica_my_reported_issues` from ADR-196 Phase 3):
registered in `ToolRegistry` but absent from `CATEGORIES` in
`mcp-server/src/tools/discovery.ts`, so layered-discovery agents (default
for ChatGPT and Claude.ai connectors with `MCP_LAYERED_DISCOVERY=1`) could
not find them via `pica_discover`. The 2026-04-25 hardening that added
blocking `checkTaxonomyCompleteness` to `scripts/lint-mcp-tools.ts`
surfaced 94 grandfathered violators in the baseline. This release retrofits
every one. Fourteen new categories — `agreements`, `collaborators`,
`custody`, `physical_assets`, `recording_splits`, `royalties_statements`,
`share_links`, `split_sheets`, `licensing`, `notifications`, `messaging`,
`agent_identity`, `telegram`, `labels` — and the tools fold into existing
ones (`releases`, `analytics`, `enrichment`, `import`, `projects`,
`profile`) where they belong. `scripts/mcp-tool-lint-baseline.json
→ missing_taxonomy` is now empty.
- **`pica_upload` and `pica_download` newly reachable via `pica_discover`** —
the two generic harness cards (ADR-152) had been silently absent from the
`CATEGORIES` taxonomy since they shipped. `pica_upload` added to the
existing `uploads` category alongside the programmatic `pica_upload_file` +
`pica_upload_complete` pair. `pica_download` added to the `export` category
since it's the delivery side of any export-tool flow.
### Changed
- **`CATEGORIES` is now exported** from `mcp-server/src/tools/discovery.ts`
so `scripts/lint-mcp-tools.ts` can read it. Compile-time only; no
consumer change.
## [2.28.1] - 2026-04-25
### Fixed
- **`pica_report_issue`, `pica_my_reported_issues`, and `pica_submit_feedback`
were unreachable via `pica_discover`** — the layered-discovery taxonomy
in `mcp-server/src/tools/discovery.ts` did not list them in any of its
26 categories, so agents using the layered flow (default for ChatGPT
and Claude.ai connectors that need to keep their context window
manageable) literally couldn't find these tools. Real-world failure
caught 2026-04-25: a Claude Desktop session dogfooding the issue-
reporting flow returned "no feedback or support tool exposed in the
PICA MCP" and suggested `pica_notes_create` as the closest match —
the exact failure mode `pica_report_issue` was designed to catch
(which it couldn't, because it wasn't discoverable). New `feedback`
category added to the discovery taxonomy listing all three tools.
`pica_submit_feedback` had been silently invisible since ADR-184
shipped — same bug class, same fix.
### Changed
- **`pica_report_issue` description tightened** to instruct the agent
on WHEN to call: PROACTIVELY without being asked when the agent itself
hits a workflow gap, AND on-demand when the user says "this is broken"
/ "this didn't work". Previously the description was descriptive
("file a structured issue when…") which left agents reading it as a
passive option rather than a recommended habit. Also adds a
cross-reference to `pica_my_reported_issues` for the resolution-poll
side of the closed loop.
## [2.28.0] - 2026-04-24
### Added
- **ADR-196 Phase 4a — `pica_my_reported_issues` tool.** Org-scoped
customer-side polling tool that closes the loop on `pica_report_issue`.
Lets the agent answer "did anyone fix that thing I reported?" without
going through the operator surface. Returns the issue, current status,
resolution_note, and occurrence_count for each issue the calling org
has reported or been impacted by. Default sort most-recently-touched
first; optional `status` filter (open / triaged / investigating /
resolved / wont_fix). Tool metadata: `category=comms`, `risk=safe`,
`retry_safe=true`. Pairs with `@withpica/mcp-sdk@1.12.0` for the new
`OpsIssuesResource.listMine()` method.
### Changed
- **`mcp-server/src/config.ts` VERSION constant is now derived from
`package.json`** via `createRequire(import.meta.url)("../package.json")`,
not a hardcoded literal. Drift-proof — future bumps no longer require
manual sync between the two files. The version-sync lint in
`scripts/lint-mcp-tools.ts` now accepts both forms (literal still
blocks on drift; derived is always valid). Motivated by two
consecutive publishes (2.27.0 + 2.28.0) being blocked by the stale
literal — the trap is now permanently sealed.
### Fixed
- **`mcp-server/src/tools/discovery.ts` no longer references
`metadata.credits_required`** — the property was removed from
`ToolMetadata` in the chore/drop-mcp-credit-gating cleanup (per
ADR-162: PICA is subscription-only) but two stale references in
`discovery.ts` slipped through, blocking the publish until fixed.
No tool carries the field anymore so the conditional spread was
dead code; replaced with a comment recording the history. The
comment itself is removed in chore/drop-mcp-credit-gating
(this PR) — credit gating is gone for good.
### Changed
- **ADR-199 Week 1 — `withBillingGate` extended to every write-tool
surface.** Previously the gate only covered `audio-files.ts`,
`exports.ts`, and `enrichment.ts` (8 tools). Now wraps 38 write
executors across 9 tool families: `agreements` (4), `agreement-types`
(5 — `renderTemplate` deliberately ungated as it's a pure read),
`custody` (4), `people` (3), `works` (5), `recordings` (3), `credits`
(1), `assets` (7 — `assetExport` deliberately ungated to match
`pica_export_catalog_csv`), and `dashboard` claim/review tools (4).
Read tools (query / list / inspect / history / search / stats /
pulse / briefing) intentionally not wrapped. Behaviour change for
agents on hibernated workspaces: write tools that previously executed
now return the lobby error. Operation labels in the lobby message
use domain terms ("agreement creation", "physical asset link",
"custody claim", "discovery review", etc.). The gate itself reads
from `organisations.billing_state` via the new effective-state route
(see `[Unreleased]` in `@withpica/mcp-utils`); `is_internal=true`
organisations bypass the gate. Rollback path is the
`ADR199_READ_BILLING_FROM_ORG` env var on the Vercel app — flipping
to `false` reverts the read source to `user_profiles.billing_state`
without an MCP redeploy.
- **ADR-199 Week 1 language sweep — agent-facing copy moves to domain
terms.** "Your organisation" / "this organisation" / "your org"
swept to "your catalog" (where the noun is the music data) or "your
account" (where no domain term fits) across `audit`, `collaborators`,
`custody`, `dashboard`, `directory`, `memory`, `metadata`,
`my-reported-issues`, `releases`, and `settings` tool descriptions.
Internal identifiers (`pica_organisation_profile`,
`pica_update_organisation_profile`, `org_type` parameter, the
`"organisation"` memory-visibility enum value) deliberately
preserved — those are wire-protocol or schema names, not copy.
Industry term "Performing rights organisation" preserved (it's a
PRO, not a withPICA org). Display names in `metadata.ts`
(`view organisation profile` / `update your organisation profile`)
swept to `view account profile` / `update your account profile`.
## [2.27.0] - 2026-04-24
### Added
- **ADR-196 Phase 3 — `pica_report_issue` tool.** Lets an agent file a
structured issue when a workflow breaks in a way raw tool failures can't
capture (`workflow_gap` / `inconsistent_result` / `missing_step` /
`data_issue` / `ux_confusion`). Auto-dedupes via the v2 signature
(sha256 of `"v2|{kind}|{canonicalised_summary}"`), so two reports of
the same problem with different wording collapse into one
`ops_issues` row. Operators triage from the same record. Raw tool
errors continue to auto-populate `ops_issues` via the `mcp-audit.ts`
upsert path — agents should NOT call `pica_report_issue` for those
(it would create a duplicate). Tool metadata: `category=comms`,
`risk=mutating`, `retry_safe=true`. Pairs with
`@withpica/mcp-sdk@1.11.0` and `@withpica/mcp-utils@1.7.0` published
in the same cycle.
### Fixed
- **`config.ts` VERSION constant bumped to 2.27.0** to match the
`package.json` version. The `version-sync` lint in
`scripts/lint-mcp-tools.ts` blocked the initial 2.27.0 publish until
this was corrected — caught a pre-existing trap where the banner
version is hardcoded in source rather than derived from `package.json`
at build time. Future bumps must update both files; consider
auto-deriving in a follow-up.
- **`pica_report_issue` tool — drop double-unwrap of SDK response.**
`OpsIssuesResource.reportIssue()` now returns the unwrapped payload
(mcp-sdk@1.11.0 tightened the return type), so the tool's previous
`result?.data || result` fallback was dead code that didn't
typecheck. Now `const data = result;` directly.
## [2.26.0] - 2026-04-22
Version leapfrogs 2.25.0 (which was published to npm without a matching commit / changelog entry in this repo, discovered during this publish cycle). 2.26.0 ships from a clean main at commit `a3c8ca870` with the full bundle below. Pairs with `@withpica/mcp-sdk@1.10.0` and `@withpica/mcp-utils@1.6.0`, both published the same day.
### Removed
- **Billing-credit tools removed from the MCP surface:** `pica_credits_balance` (credits.ts), `pica_credits_history` (settings.ts), `pica_credits_purchase` (purchases.ts tombstoned to an empty class). See `docs/MCP_REFINEMENT_LOG.md` entry B-001. withPICA is subscription-only at £9.99/mo; surfacing a credit-balance + credit-history + credit-purchase tool trio contradicted the billing model and misled agents (caught 2026-04-22 when an agent transcript opened with "998,894 credits — plenty. Running the export now."). The underlying SDK methods (`creditsBalance.*`, `settings.creditsHistory`) and `lib/services/credits/**` infra remain intact — only the MCP tool surface shrinks. `pica_credits_list` and `pica_credits_update` are **kept**: they're work_credits (songwriter roles + splits on a work), not billing credits.
- **`CREDIT_INSUFFICIENT` recovery hints pointing at `pica_credits_balance`** removed from `recovery-hints.ts` for `pica_resolve_work`, `pica_export_song_registration`, `pica_export_catalog_asset_report`, `pica_audio_analyze`. Paired tests under `__tests__/tools/recovery-hints.test.ts` + `settings.test.ts` updated to assert the new shape.
### Changed
- **Credit-gating language stripped from tool descriptions** — `pica_audio_identify` ("Costs 3 pica credits on match…"), `pica_export_song_registration` ("Costs 1 pica credit."), `pica_export_industry_ready` (same), `pica_export_catalog_asset_report` ("Costs 5 pica credits…"), and the 3 `pica_resolve_*` tools in `enrichment.ts` ("Costs 1 pica credit per call."). The `withCreditGate` / `withBillingGate` runtime wrappers are **left in place** — only the agent-facing prompt-gating copy goes. Same B-001 refinement entry.
- **Server instructions** (`src/server-instructions.ts`, mirrored in `lib/mcp/server-instructions.ts` via the parity test) open with a one-line disambiguation from `@picahq/mcp` (Pica AI / picaos.com) — a different product whose short name collides with ours. Helps agents that have both connectors installed (or users who pasted their withPICA key into the wrong config) recognise which server they're actually talking to. See the picaos name-collision memory rule.
- **`pica_catalog_diligence` now returns structured JSON** instead of failing with a JSON-parse error on binary ZIP bytes. The fix lives in `@withpica/mcp-sdk`'s `AnalyticsResource.catalogDiligence()` — the SDK now requests `?format=json`. See the mcp-sdk CHANGELOG for detail. MCP refinement log: "Singleton — `pica_catalog_diligence` returns ZIP parsed as JSON".
### Fixed
- **ADR-174 Phase 2c — inverse phantom in `pica_works_update` (works slice).** Three jsonb compliance fields (`ai_disclosure`, `provenance_attestation`, `training_rights`) are satellite-routed to `work_licensing` via `WORK_SATELLITE_FIELDS` and accepted by both the HTTP route allow-list and the service layer, but were missing from `WORK_WRITE_PROPERTIES`. Agents following the `pica_works_update with ai_disclosure: ...` completion hint at works.ts:577 were hitting schema-level silent-drop (or strict-validator 400s). Added all three properties to the write schema with shapes copy-pasted from the parallel fields on `pica_recordings_update` (recordings.ts:193). Tool description updated to distinguish audio-trait satellite fields (still not exposed) from the three compliance satellite fields (now exposed). Full audit of the other 22 customer-MCP write tools yielded zero further inverse phantoms — see `docs/audits/2026-04-21-mcp-inverse-phantom-audit.md`.
### Added
- **ADR-189 Phase 4 slice 3: `pica_claim_artist` MCP tool.** INSTANT path for artist-claim on discovery-backed evidence. Wraps `artistClaimingService.processClaimDecision` to reuse the existing `work_claims.artist_claim_status` / `claimed_by_person_id` / `claim_confirmed_at` update path + `performer` work_credits insert. Refuses (409 `ADMIN_REVIEW_IN_PROGRESS`) when an open admin `artist_claims` row exists for the same (work, person) — returns `admin_review_id` in the body so the agent surface can route the user to resolve it. Rate-limited to 20/hour per user via the shared `claim_write` bucket. Wrapped in `withAgentActionStamp` when grant-authed — rollback captures pre-write `work_claims` row + tracks whether a new performer credit was inserted (deleted on compensation) or pre-existed (left alone). **Schema drift carryover:** the plan proposed capturing rollback state from 4 `works` columns, but live staging (`rtxcxazevrgcjnudvggf`) keeps the artist-claim lifecycle columns (`artist_claim_status`, `claimed_by_person_id`, `claim_confirmed_at`) on the `work_claims` satellite — only `primary_artist_id` lives on `works`. Service captures + restores from `work_claims` accordingly. The live `artist_claims.claim_status` CHECK constraint has no `under_review` state (only `pending | approved | rejected | withdrawn`), so the open-review gate checks `claim_status = 'pending'` only.
### Removed
- **ADR-154 F13: seven deprecated per-source enrich tools removed.** `pica_enrich_work_{mlc,musicbrainz,discogs,spotify,youtube}` and `pica_people_enrich_{isni,musicbrainz}` (tombstoned since ADR-179 Phase 1/2, 2026-04-17) are no longer registered — every call has returned a `-32070 TOOL_DEPRECATED` error for a full minor cycle, ending the one-minor deprecation window per ADR-179 Decision 2. Callers must use `pica_resolve_work(sources: [...])` or `pica_resolve_person(sources: [...])` instead; the `tools/list`, `metadata`, `recovery-hints`, and category-`discovery` surfaces shed the seven stale entries in this release. Downstream impact: clients that hardcoded the tool names now get `tools/list`-level absence rather than a deprecation envelope — fail-fast instead of fail-with-suggestion.
### Changed
- **ADR-154 F14: completion-hint sweep for the seven removed tools.** Updated every `→ then:` and hint-string reference that pointed at the F13 tombstones across `enrichment.ts`, `people.ts` (3 sites), `collaborators.ts`, `works.ts` (2 sites), `search.ts`, `server-instructions.ts`, `prompts/index.ts` (3 sites), and `resources/llms-primer.ts`. Agents discovering tools via descriptions will no longer be trained toward dead tool names. The ADR-179 "Replaces the five/two per-source tools" phrasing is gone from the resolver descriptions too — resolvers stand on their own now.
- **ADR-180 Phase 3: full TOOL_METADATA coverage.** 29 tools retrofitted in `mcp-server/src/tools/metadata.ts` across eight family-scoped commits — resolve (3), cascade (3), enrichment proposals (4), collaborators (4), notify-user scheduling (3), physical-assets links (4), misc singletons (5), and the ADR-185 Part 1 agent-identity drift (3). `scripts/mcp-tool-lint-baseline.json` is deleted; MCP Tool Lint now runs in fully-strict mode. No behaviour change — tool discovery, audit categorisation, and risk-tier gating now classify the same 29 tools they couldn't classify before. `credits_required` deliberately omitted from new entries (credit gating is a no-op pass-through since ADR-162). Retry-safety follows ADR-180's `risk × retry_safe` table — e.g. `pica_enrichment_proposal_apply` is `retry_safe: false` (writes to entity columns), `_reject` is `retry_safe: true` (content-hash suppression is set-once). ADR-180 flipped from `Proposed` to `Accepted`.
- **ADR-193 Surfaces 1+3: peer-connector coaching + URI source conventions (server-instructions addendum).** Handshake copy coaches agents to check peer MCPs (email / drive / notes / calendar / DAW) before asking users to paste or type data, and to cite findings via `pica_enrichment_propose` `sources[].url` using URI schemes — `gmail://` `gdrive://` `notion://` `file://` `telegram://` `calendar://` `daw://` `user://` `web://`. See `docs/mcp-source-uri-conventions.md` in the main repo for shapes. First concrete build for ADR-190 Principles 2 + 3 (Agent-Mediated Source Discovery). A latent HTTP-transport gap was fixed in the same develop commit (`63eef17f8`) — `/api/mcp/route.ts` was not wiring `SERVER_INSTRUCTIONS` into its Server constructor, so remote agentic clients (ChatGPT Apps, Claude Desktop remote, Cursor) saw zero coaching. The stdio transport in this package was always serving instructions correctly; only the remote transport was affected.
### Added
- **ADR-193 Surface 2: `gap_hints` envelope on 4 read tools.** `pica_works_inspect`, `pica_agreements_inspect`, `pica_people_inspect`, and `pica_enrichment_proposals_list` now emit an optional `gap_hints[]` field on `structuredContent` when a real gap is present. Each hint is capability-named (ADR-190 Principle 2 — `email_search` / `drive_search` / `filesystem_search` / `notes_search` / `calendar_search` / `web_search`, never vendor names), carries a concrete `query_shape`, and pre-fills the `then_call: "pica_enrichment_propose"` with the exact `then_args_shape` the agent should emit back (including URI-citation scaffolding from Surface 3). Gated by `MCP_GAP_HINTS_ENABLED=1` — off by default in prod. Empty array / omitted envelope when there's no real gap. Flat additive — the existing `CompletionHint` pipeline on `works_inspect` is untouched. Bundled: `enrich-metadata` prompt references the peer-connector pattern and points the agent at `gap_hints`.
- **ADR-193 Surface 4: `pica_enrichment_propose` accepts `proposal_action: "create"`.** Closes the reserved-but-unwired create path from ADR-178 Phase 1. The tool schema now accepts `entity_type: "recording" | "agreement" | "person"` paired with `parent_entity_type` + `parent_entity_id` for create proposals. Apply-path branches on `rule_id === "agent_research"` and routes to `recordingsService.create` (parent required; `work_id` linkage), `agreementsService.create` (optional work parent, linked via `agreementWorksService.addWork` after create), or `peopleAdminService.create` (standalone — people have no parent FK). Recording creates seed `field_sources` verbatim from `proposal.sources[].url`, preserving URI schemes (gmail://, gdrive://, notion://, file://, telegram://, user://, web://) per ADR-193 AC 5. Agreements + people don't carry `field_sources` on the main table today; provenance URIs are captured in `resolution_note` until a follow-up migration adds the column. Work + publisher creation remain explicitly out of scope (dedup-critical — deferred to future ADR). Migration `20260422_01_adr193_proposal_parent_fields` adds nullable `parent_entity_type` + `parent_entity_id` columns with a CHECK pair + partial index for the review-queue scan. Legacy `artist_title_to_youtube` create rule is untouched (it continues to overload `entity_id` as the parent work id). 4 new service-layer tests green; total proposal-service suite 42/42.
- **ADR-185 Part 1: three new session-auth-only tools.** `pica_create_agent_identity`, `pica_issue_agent_grant`, `pica_list_my_agent_grants`. Back a user-issued agent-identity + scoped-grant primitive — the substrate for Parts 2 (settings UI) and 3 (per-write provenance stamping). All three require `write:agent_identity` scope AND session-auth (raw API key / OAuth); the HTTP MCP dispatcher refuses `pica_grant_` callers with a structured `reason: "session_auth_required"` error before execution per AC 12. Subject is always the authenticated user — caller-supplied `user_id` is dropped per ADR-184 rule 2 / ADR-185 rule 1. Grant tokens use the format `pica_grant_<64 hex>` and are hashed at rest with HMAC-SHA256 + `API_KEY_HASH_PEPPER` (same scheme as api_keys per ADR-185 line 193 + ADR-167 D-5).
- **ADR-185 Part 2: two new session-auth-only tools.** `pica_revoke_agent_grant` (DELETE `/admin/agent-grants/:id`, idempotent, 404 masks ownership failures) and `pica_get_agent_activity` (GET `/admin/agent-activity`, paginated, optionally filtered by identity or grant). Both appended to `SESSION_AUTH_ONLY_TOOLS` in the HTTP MCP dispatcher and to the `write:agent_identity` scope bucket. Together with the Part 1 trio this completes the five-tool agent-identity surface the ADR Scope requires. Revocation takes effect on the next request (AC: within 60s — `resolveGrantToken` checks `revoked_at` on every call, no cache layer).
- **ADR-185 Part 3 passes 1-5: agent-action provenance stamping wraps 27 write-tool families.** Part 3 layers two complementary wrappers over existing write tools so every per-grant write records a row in `mcp_agent_actions` for audit + revoke-replay. `withAgentActionStamp` (compensation-after): handler runs, stamps; on stamp failure, compensation undoes the write via captured prior state — used for creates and updates (soft-delete families get meaningful delete compensation — flip `is_deleted` back). `withAgentActionStampFirst` (stamp-first, pass 4): stamp commits BEFORE the destructive op; stamp-ok + handler-fail logs a "ghost stamp" at error level — used for hard-delete families where compensation is impossible. Both wrappers are no-ops when the caller is not an agent grant (session-auth / raw API key writes are unaffected). Families covered across the five passes: works/recordings/people/agreements/releases/notes creates; notes_delete; sessions_create; share_links_create; projects_create; multimedia_create; recording_splits_create; works_update; recordings_update; people_update; agreements_update; projects_update; notes_update; releases_update; people_delete; publishers_create; the six hard-delete families (works/recordings/agreements/projects/sessions/releases) via the stamp-first variant; and pass 5's `pica_projects_attach_works` (project_works insert wrapped, GET + POST migrated from `requireAuth` + `getUserProfile` to `withOrgContext` for consistency). `pica_sessions_update` remains deferred — action-branching route (start/complete/cancel + participant add/remove + regular update) needs a dedicated design slice before wrapping.
- **New tool** `pica_submit_feedback`: agent-native feedback submission. Routes bug_report→devops, feature_request→ceo, question→comms, other→category-mapped. Requires `write:feedback` scope. Subject is always the authenticated user per ADR-184 rule 2. (ADR-184 slice 8 — landed on develop 2026-04-19 post-2.23.0 publish.)
- **ADR-189 Phase 3: two new identity-capture tools.** `pica_update_my_identity` (stage_name / IPI / ISNI / IPN / PRO) and `pica_update_organisation_profile` (org_type / display_name / tagline / ipi_number). Both admin-scoped, both `retry_safe: true` (idempotent upserts — re-applying the same IPI leaves the row in the same state). Subject derived from auth (no `user_id` / `organisation_id` parameters — ADR-184 rule 2). After the identity write, `pica_update_my_identity` re-runs cross-org discovery and returns the count of newly-visible credits / custody / artist-link rows so the agent can surface "found N more — claim them?" in the same conversation turn. Rate-limited at 20 writes/hour/user via `checkActionRateLimit` (R1 mitigation). Admin routes (`POST /api/admin/my-identity`, `POST /api/admin/organisation-profile`) wrap in `withAgentActionStamp` when the caller presents an agent grant (ADR-185 Part 3) — UPDATE compensation captures pre-write state via read-before-write and PATCHes the row back on stamp failure, or deletes a lazy-created person row.
- **ADR-189 Phase 4 Task 1: `pica_claim_credit`.** New activation-loop tool that drains a `discovered_credits` row into a `work_credits` row in the calling org, giving the user `view` access to the linked work. Check-and-set on `discovered_credits.status` prevents double-claims in concurrent sessions. Rate-limited to 20/hour per user via `checkActionRateLimit`. Returns `is_first_claim: true` on first-ever claim by the user (derived from `agent_action_log` stamp count). Wraps in `withAgentActionStamp` for grant callers — compensating transaction calls `revertClaimCredit` (flips discovery back to `pending` + deletes the `work_credits` row) if the stamp INSERT fails. Error taxonomy: `DISCOVERY_NOT_FOUND` (404), `DISCOVERY_ALREADY_RESOLVED` (409), `EVIDENCE_REVOKED` (410), `RATE_LIMITED` (429 + `Retry-After`). `pica_claim_credit` added to `ADMIN_ONLY_TOOLS` in `mcp-scopes.ts`; metadata entry `category: "discovery", risk: "mutating", retry_safe: false`. Route: `POST /api/admin/discoveries/[id]/claim-credit`. SDK method: `pica.discoveries.claimCredit(id)`.
- **ADR-189 Phase 4 Task 2: `pica_claim_custody` + +72h auto-approve worker.** Drains a `discovered_custody` row into a pending `custody_claims` row (status=`pending`, `chain`=custody_type, `custody_source`=`claimed`, provenance in `evidence.source`=`"auto-discovered-via-email"`). Schedules a silent-consent auto-approve by inserting a `scheduled_notifications` row (`source=pica_claim_custody`, `deliver_at`=+72h, message=`custody_auto_approve:<uuid>`). Does NOT mutate `works.{composition,master}_custodian_org_id` immediately — the 1-minute cron fires `scheduledNotificationsService.drainDue()` which now dispatches `source=pica_claim_custody` rows to a new `handleCustodyAutoApprove` branch that runs the **Delta 1 stale-promotion guard**: UPDATE `works` custodian columns ONLY IF the snapshot captured at claim time (`expected_previous_custodian_org_id` + `expected_custody_source` + `custody_side` — migration `20260421_01_custody_claims_expected_previous`) still matches; otherwise flip the claim to `superseded` (new status) and leave the catalog untouched. Error taxonomy identical to `pica_claim_credit`. `pica_claim_custody` was already in `ADMIN_ONLY_TOOLS` since Phase 4 Task 0; this ships the actual wiring. Route: `POST /api/admin/discoveries/[id]/claim-custody`. SDK method: `pica.discoveries.claimCustody(id)`. Stamp compensation via `revertClaimCustody` flips discovery back to pending, cancels the scheduled_notification, deletes the custody_claims row.
## [2.24.0] - 2026-04-21
### Fixed
- **`pica_integrations_status` no longer tells users to "connect Spotify".** Two real-user failures on Claude Desktop (2026-04-21) traced back to the tool lumping platform-level Spotify/YouTube (PICA has its own credentials; no per-user OAuth exists or has ever existed) into the same "Available to connect" list as genuine user-OAuth peer connectors (Google/Notion/Airtable/Telegram). Failure modes observed: (1) user pasted a Spotify track URL → agent replied "head to your Pica dashboard and add Spotify as an integration"; (2) user asked "what songs do I have in pica" with a catalog holding 1 work → agent replied "you currently have no connections set up in Pica — so there are no songs or any other data accessible." Reshape: the tool now returns `peerConnectors` (the 4 actual OAuth services) separately from `platformReads` (Spotify, YouTube, MusicBrainz, MLC, ISNI, Discogs, with the tool name for each). Summary leads with "PICA catalog is ALWAYS accessible via pica_works_query, pica_people_query, pica_dashboard_briefing, pica_search_all — no setup required." The dead `connectedProviders.includes("spotify")` check that could never evaluate true was removed.
- **Server instructions: catalog-is-always-accessible guidance.** Handshake now explicitly states (a) the catalog is always accessible via the read tools with zero setup, (b) `pica_integrations_status` showing "0 connected" refers to optional peer connectors, NOT the catalog — call `pica_works_query` to check, (c) Spotify/YouTube URLs go through `pica_import_streaming_link` / `pica_import_youtube_link` directly; no per-user Spotify/YouTube OAuth exists to set up, do not tell the user to "connect Spotify in your dashboard". Replaces earlier copy that left the agent free to hallucinate a dashboard-integration flow. `lib/mcp/server-instructions.ts` parity copy updated in lockstep; the HTTP transport at `/api/mcp` picks up the new text on next handshake without requiring a redeploy of this package. Under the 2000-char token budget at 1833 chars.
## [2.23.0] - 2026-04-19
### Added
- **ADR-181 Phase 1: `pica_audit_list` ported from `@withpica/mcp-server-settings`
(ADR-154 F6).** Closes the one trailing-gap tool from the source-merge audit —
every other tool the three sub-servers exposed was already registered by
`mcp-server`'s superset. Registered in `lib/services/mcp-scopes.ts`
`ADMIN_ONLY_TOOLS` alongside the team / org-profile admin operations; the
`mcp_audit_log` read is org-scoped and matches the existing admin posture
for `pica_organisation_profile` / `pica_team_list`. Landed in `54ae935d9`
via PR #50.
- **ADR-181 Phase 1: bundle-size CI gate per Decision 2.** New
`scripts/check-mcp-bundle.ts` walks `mcp-server/dist/`, sums byte size
(excluding source maps), and fails if total > 210 MB (70% × Vercel's
300 MB serverless-function cap). Runs on every push / PR that touches
`mcp-server/**`, `mcp-server-shared/**`, the script, or the workflow
itself (`.github/workflows/mcp-bundle-size.yml`). `package.json`
`prepublishOnly` invokes it locally so a bloated bundle can't ship out
of a developer machine. Current measurement on this branch:
0.79 MB / 210 MB limit (0.38% of cap). Landed in `558315069` via PR #50.
## [2.22.0] - 2026-04-18
### Added
- **ADR-174 Phase 2 item 7 follow-up: `pica_projects_attach_works` (stdio).**
Closes the `work_ids` FIELD_NOT_WIRED gap on `pica_projects_create` /
`_update`. Pre-slice the MCP tools advertised `work_ids` but the admin
route silently dropped it — the route redirect message already pointed
agents at `POST /api/admin/projects/[id]/works`, but no MCP tool
exposed that path. The new tool accepts
`{ project_id, works: [{ work_id, project_day?, notes? }...] }` and
attaches each item via `ProjectsResource.attachWork` with per-item
result reporting (`attached[]`, `skipped[]` for already-linked,
`failed[]` for other errors). Registered in `lib/services/mcp-scopes.ts`
under `write:catalog`.
### Changed
- **ADR-174 Phase 2 item 22: `pica_publishers_create` reshaped.** Pre-slice
the schema exposed only `{ name, ipi }` (2 fields) and the SDK's
`PublishersResource.create` hit a 404 — the admin route did not exist.
The companion slice 1 commit added `POST /api/admin/publishers`;
this slice reshapes the tool to match. All 8 audit-row-22
Add-candidates surfaced (`legal_name`, `country`, `isni`,
`publisher_type`, `wikidata_id`, `parent_publisher_id`,
`founded_date`, `headquarters`) plus the explicit `ipi → ipi_number`
rename. `parent_publisher_id` description steers agents at
`pica_publishers_query` to resolve the parent UUID. Required fields:
`name`, `ipi_number`. The route is idempotent on `ipi_number` — a
duplicate IPI returns the existing row with `already_existed: true`
instead of failing.
- **ADR-174 Phase 2 items 14+15: `pica_agreements_create` / `_update`
reshaped to consume `AGREEMENTS_WRITE_PROPERTIES` from
`@withpica/mcp-utils`.** Tool schemas now advertise all 13
Add-candidate columns from audit rows 14+15. Tool descriptions steer
agents toward `counterparty_org_id` (ADR-176 first-class-party) over
free-text `other_party_name` — use `pica_search_all` to resolve an
org name to an ID. `_update` description calls out that
counterparty_org_id changes require the linked-org check at the
route layer (orgLinkingService.areLinked). No tool signature change
otherwise; existing fields preserved.
### Route-side companion (non-package, same slice)
- `app/api/admin/agreements/route.ts` POST and
`app/api/admin/agreements/[id]/route.ts` PATCH — phantom/unknown key
rejection via `AGREEMENT_POST_ALLOWED_FIELDS` /
`AGREEMENT_PATCH_ALLOWED_FIELDS` /
`AGREEMENT_WRITE_PHANTOM_REDIRECTS` in
`lib/services/agreements-write-constants.ts`. Pre-slice both routes
spread `body` straight into the Supabase insert/update — effectively
a wide-open write surface. Also surfaces and fixes pre-existing dark
code in the PATCH test file (3 tests were sending `name` and `type`
— phantom field names that silently dropped against `title` and
`agreement_type`).
- `app/api/admin/publishers/route.ts` POST (NEW) — publishers had no
admin route at all pre-slice, so `pica_publishers_create` and the
SDK's `PublishersResource.create` 404'd at runtime. Slice 1
(0ba4d400d) adds the route with phantom/unknown-field rejection via
`PUBLISHERS_POST_ALLOWED_FIELDS` / `PUBLISHERS_WRITE_PHANTOM_REDIRECTS`
in `lib/services/publishers-write-constants.ts`, idempotent
deduplication by `ipi_number`, FK pre-validation on
`parent_publisher_id`, and server-stamped provenance.
- **ADR-174 Phase 2 item 21: `pica_multimedia_create` reshaped.** Added
15 real-column fields that were invisible to agents pre-ADR-174:
`credits`, `duration_seconds`, `collection_id`, `display_order`,
`is_featured`, `is_published`, `venue`, `event_name`,
`performance_date`, `setlist_position`, `sync_side`, and the external
identifier set (`spotify_url`, `spotify_track_uri`, `spotify_track_id`,
`youtube_url`, `youtube_video_id`, `soundcloud_url`, `thumbnail_url`).
Description now points agents at `POST /api/admin/multimedia/upload`
for file uploads and `pica_multimedia_link_youtube` for the YouTube
enrichment flow — the generic create surface is for external-URL
linking and performance-context metadata.
### Route-side companion (non-package, same slice)
- `app/api/admin/multimedia/route.ts` POST — phantom/unknown key guard
via `MULTIMEDIA_POST_ALLOWED_FIELDS` /
`MULTIMEDIA_POST_PHANTOM_REDIRECTS` in
`lib/services/multimedia-write-constants.ts`. Redirects upload-flow
columns (s3*key, file_name, …) to `/api/admin/multimedia/upload`,
enrichment columns (youtube_view_count, spotify_popularity) to their
respective cascades, and scanner columns (virus_scan*\*) to the
scanner flow. Also closes a latent privilege-escalation path: pre-slice
the route overwrote `body.user_id` with the authenticated session's
id, but a caller could still _attempt_ to pass `user_id` (silently
overwritten). The guard now 400s on `user_id` / `organisation_id`
injection attempts with a clear PHANTOM_FIELD message.
- **ADR-174 Phase 2 item 13: `pica_sessions_create` reshaped.** Dropped
`additionalProperties: true` per Decision 2. Added 6 previously-missing
real columns from audit row 13 as explicit Zod properties:
`description`, `timezone`, `is_all_day`, `location_url`, `recording_id`,
`status` (enum: scheduled / in_progress / completed / cancelled), plus
the `metadata` jsonb bag and the `participants` relational array
(already handled by the service but undocumented in the tool surface).
`recurrence_rule` deferred — the service's `CreateSessionParams` type
needs to accept it first (tracked in the route-guard PHANTOM_REDIRECT
entry).
### Route-side companion (non-package, same slice)
- `app/api/admin/sessions/route.ts` POST now reads `body.status` and
`body.metadata` (pre-slice silently dropped) and 400s on phantom /
unknown keys via `SESSION_POST_ALLOWED_FIELDS` /
`SESSION_POST_PHANTOM_REDIRECTS` in
`lib/services/sessions-write-constants.ts`.
- **ADR-174 Phase 2 item 20: `pica_share_links_create` reshaped to
consume `SHARE_LINKS_WRITE_PROPERTIES` from `@withpica/mcp-utils`.**
Tool schema now advertises all 13 add-candidate columns from audit
row 20 that were previously invisible to agents (access_type,
allowed_person_ids, allow_comments, allow_view_collaborators,
allow_view_financials, notify_on_access, watermark_audio,
sent_to_emails, playlist_name, allow_streaming, include_all_files,
included_file_ids, description). Executor widened to pass every
caller-provided property through to the SDK / admin route — the
route-layer allow-list (slice 1) rejects anything outside the
contract. `password` remains the canonical plain-text input; the
route hashes to `password_hash`.
- **ADR-174 Phase 2 items 16+17: `pica_agreement_types_create` / `_update`
reshaped to consume `AGREEMENT_TYPES_WRITE_PROPERTIES` from
`@withpica/mcp-utils`.** Dropped the inlined property map in favour of
the shared module that documents per-branch applicability. Phantom
fields `title`, `description`, `points_percentage`, `recoupment_terms`,
`rights_assigned` removed from the schema (agents sending them now fail
fast at the admin route guard with a `PHANTOM_FIELD` redirect pointing
at the canonical column or resolution path). Tool descriptions updated
to truthfully list the required fields per branch (template needs
`name` + `category` + `templateText`; producer needs `producer_id` +
`royalty_points` + `deliverables` + `work_id`|`recording_id`;
work_for_hire needs `contractor_id` + `contractor_role` + `fee_amount`
- `services_description` + `work_id`|`recording_id`). Tool description
for `_update` now calls out that PATCH on producer_agreements and
work_for_hire_agreements is action-only — the generic update path has
no live field-update surface on those two branches until a follow-up
slice promotes the action-based PATCH handlers to accept field
updates.
### Added
- **ADR-179 Phase 1: `pica_resolve_work` (stdio).** Outcome-shaped
resolver that fans a work out across every eligible enrichment source
in one call. Replaces the five `pica_enrich_work_*` tools' role at
the agent layer. Returns `applied[]` / `proposals[]` / `errors[]` /
`recovery_hints[]`. `isError` is set only when every source that ran
errored — partial failure is a non-error receipt. Credit + billing
gates wire through the `external_resolver_call` action key.
- **ADR-179 Phase 2: `pica_resolve_person` (stdio).** Person-side
resolver mirroring `pica_resolve_work`. Sources are narrower —
`isni` | `musicbrainz` — covering the identity-graph cascade rules
(ISNI→Wikidata and MBID→Wikidata crosswalks plus their downstream
awards / biography enrichment). Same output shape, same isError
semantics, same credit-gate action key as the work resolver.
Registered in `lib/services/mcp-scopes.ts` under `write:people` and
listed in the layered-discovery `enrichment` category so
`pica_discover` surfaces it above the deprecated
`pica_people_enrich_*` pair.
### Deprecated
- **`pica_enrich_work_mlc`, `pica_enrich_work_musicbrainz`,
`pica_enrich_work_discogs`, `pica_enrich_work_spotify`,
`pica_enrich_work_youtube`.** All five now return
`TOOL_DEPRECATED` (jsonrpc code -32070) with a suggested-call
payload pointing at `pica_resolve_work` with the correct `sources`
narrowing. Tool definitions stay registered so `tools/list` and
`pica_tool_details` can still surface them with a migration hint for
one minor version — removal scheduled for the next minor after
ADR-179 Phase 4's publish.
- **`pica_people_enrich_isni`, `pica_people_enrich_musicbrainz`.**
Both now return `TOOL_DEPRECATED` (jsonrpc code -32070) with a
suggested-call payload pointing at
`pica_resolve_person(sources: ['isni' | 'musicbrainz'])`. Tool
definitions stay registered until the next minor; same one-minor-
version deprecation window as the work-side five.
### Changed
- `pica_tool_details` responses for the five deprecated work tools
now carry a `TOOL_DEPRECATED` entry in `recovery_hints` as the first
item. Existing `NO_MATCH` / `MISSING_INPUT` hints are kept behind
it — they are dead code today but stay in the registry until tool
removal so downstream consumers don't see a mid-version
disappearance.
- Same treatment applied to the two deprecated person tools:
`pica_people_enrich_isni` and `pica_people_enrich_musicbrainz` each
get a `TOOL_DEPRECATED` recovery hint prepended to their existing
`NO_MATCH` entry.
## [2.22.0] — 2026-04-16
### Changed
- **ADR-174 Phase 2 item 10 (recording_splits): reshape
`pica_recording_splits_create`.** Adopts the shared
`RECORDING_SPLITS_WRITE_PROPERTIES` from `@withpica/mcp-utils@1.4.0`,
dropping `additionalProperties: true` per Decision 2. All 9 audit-
Keep fields now enumerated as explicit Zod properties — no phantom
additions, no renames. Description-level hint on `role` replaced
with a proper `enum` array matching the route's validRoles allow-
list. Schema is now byte-identical to `@withpica/mcp-server-business`
(both consume the shared module) — pre-slice they were independent
byte-divergent-risk copies.
### Route-side companion (non-package, same slice)
- `app/api/admin/recordings/[id]/splits/route.ts` POST — UNKNOWN_FIELD
guard runs before the required-field check so misspelled keys fail
fast with the field name. See the slice commit.
## [2.21.0] — 2026-04-16
### Fixed
- **ADR-174 Phase 2 item 24 (team): `pica_team_update_role` now actually
updates role.** Pre-ADR-174 the tool was named `_update_role` but its
Zod schema only accepted `permissions` — the `role` field was missing
entirely. The admin PATCH route at `/api/admin/team/[id]` hard-
destructured `{ permissions }` and silently dropped every other
field, so even if an agent hand-crafted a call with `role` the DB
write would not happen. Audit row 24 flagged this as "Add `role`, or
rename the tool." Fixed by adding `role: string` to the MCP Zod
schema, widening the route to build the update object from whichever
of `role` / `permissions` are provided, and making the old
"permissions required" check accept "at least one of role or
permissions." `id` stays required; `role` and `permissions` are now
both optional but at least one must be set.
### Route-side companion (non-package, same slice)
- `app/api/admin/team/[id]/route.ts` PATCH — phantom/unknown key
rejection via `TEAM_MEMBERSHIP_PATCH_ALLOWED_FIELDS` (allow-list of
`permissions` + `role`), error message updated from "permissions
required" to "at least one of permissions or role is required", and
"failed to update permissions" to "failed to update team membership"
(now-generic — it updates role too).
## [2.20.0] — 2026-04-16
### Changed
- **ADR-174 Phase 2 item 7 (projects): reshape `pica_projects_create` /
`pica_projects_update` Zod schemas.** Exposed 9 Add-candidate fields
from audit row 18 that were already plumbed end-to-end through the
route and service but invisible to agents via tool discovery:
`start_date`, `end_date`, `location_name`, `location_address`,
`location_url`, `virtual_url`, `is_open`, `max_participants`, `status`
(enum: draft / scheduled / active / completed / cancelled). Also
surfaced the route-level `generate_invite_code` flag and the
freeform `metadata` jsonb object. Dropped `work_ids` from both
schemas — the pre-ADR-174 tool advertised `work_ids` but the admin
POST / PATCH routes never routed it to the `project_works` junction
(see commit `edf890a24` for the route-side FIELD_NOT_WIRED rejection
and redirect message pointing at
`POST /api/admin/projects/[id]/works`). The `project_type`
description replaces the pre-ADR-174 "album, ep, compilation, single,
mixtape" hint (none of which are valid backend values) with the
actual convention: writing_camp, recording_session, production,
mixing, mastering, retreat, other.
### Route-side companion (non-package, same slice)
- `app/api/admin/projects/route.ts` POST and
`app/api/admin/projects/[id]/route.ts` PATCH — phantom/unknown key
rejection with `FIELD_NOT_WIRED` redirect for `work_ids`. See commit
`edf890a24`.
## [2.19.0] — 2026-04-16
### Added
- **ADR-178: `pica_enrichment_propose` stdio tool.** New agentic write
tool that files enrichment proposals sourced from open-web research
(Audiomack, Songdata, MusicBrainz page, label portals, artist bios,
etc.) rather than from PICA's structured cascade. Every proposed
field must cite at least one source URL — `sources[].fields` arrays
form the trust moat, and the server rejects uncited claims with
`code: MISSING_SOURCE`. Source type routes the proposal as
`source = 'agent_research'`, `rule_id = 'agent_research'` on the
existing `enrichment_proposals` table (no new queue, no new review
tools — reuses `pica_enrichment_proposals_list` / `_apply` /
`_reject` from `2.10.0`).
Tool description carries ADR-178 Decision 3 language verbatim: when
the user is in-session, the agent asks for approval directly and
calls `pica_enrichment_proposal_apply` after confirmation — the
dashboard card is the receipt, not the workflow. This shapes agent
behaviour in-context without needing a new code path for "in-
session approval".
Wrapped in `withBillingGate` and (via
`@withpica/mcp-utils@1.3.0`) `withCreditGate` under the new
`enrichment_propose` action key. Credit gating is telemetric only
while ADR-162 keeps the subscription gate on; the wrap is already
in place so re-enabling billing doesn't need a tool-surface change.
### Fixed
- **ADR-178 slice 1b regression caught in smoke test:** agent-research
proposals whose `proposed_fields` cite columns that don't exist on
the target table (tempo_bpm, musical_key, producers — the Heinzmann
transcript's fields, not columns on `works`) used to 500 with a
Postgres "column does not exist" error because the service built a
column-scoped `SELECT` from the proposed keys. Fix snapshots the
full live row and builds `current_fields` from the intersection of
proposed keys and actual columns. Unknown keys now land as pending
proposals with an empty baseline instead of crashing the request.
Apply path for unknown-column fields is a separate open question on
the ADR (see Open Question 5).
## [2.18.0] — 2026-04-16
### Changed
- **ADR-174 Phase 2 item 6: reshape `pica_credits_update` Zod schema.**
Dropped phantom `publisher_name` and `publisher_ipi` properties —
`work_credits` has no columns for either. Agents now resolve a
publisher name or IPI to a UUID via `pica_publishers_query` (or
create one via `pica_publishers_create`) and pass the result as
`publisher_id`. Added three audit "Add candidate" columns to the
per-credit-object shape: `is_primary` (boolean), `notes` (string),
`attestation_status` (enum: pending / attested / disputed /
declined). `attested_at` / `attested_by` remain service-managed.
Description refresh calls out `credit_type` as the canonical column
name stored behind the `role` alias and `writer_split_percentage`
behind `splits`. No required-field change — `person_id`, `role`,
`splits` stay required.
### Route-side companion (non-package, same slice)
- `app/api/admin/works/[id]/credits/route.ts` POST — phantom/unknown
key rejection on each `credits[i]` element with field name + index
in the error payload, plus a mirror-fill step that normalises
`role` ↔ `credit_type` and `splits` ↔ `writer_split_percentage` so
callers sending either spelling flow through. See commit
`dbfb098ae` for the server-side guard.
### Not covered by this slice
- `work_collaborators` (publishing-side POST at
`app/api/admin/works/[id]/collaborators/route.ts`) was not in the
ADR-174 audit — the MCP tool's `updateCollaborators` SDK hop hits
that route for writer/composer/lyricist/arranger roles today and
inherits a separate column set. Tracked as a follow-up slice.
## [2.17.0] — 2026-04-16
### Changed
- **ADR-174 Phase 2 item 5: reshape `pica_physical_assets_create` and
`pica_physical_assets_update` Zod schemas.** Adopted the shared
`PHYSICAL_ASSETS_WRITE_PROPERTIES` from `@withpica/mcp-utils@1.2.0`,
dropped `additionalProperties: true` on both tools, and moved 11
phantom top-level fields (`current_valuation`, `valuation_currency`,
`valuation_source`, `valuation_date`, `valuation_notes`,
`reverb_listing_url`, `ebay_item_url`, `insured`,
`insurance_provider`, `insurance_policy_number`, `insurance_coverage`)
off the top-level surface. Agents set these via
`external_references: { current_valuation: 1500, insurer: "ACME", ... }`
per the ADR-174 Open Decision 1 (2026-04-14) resolution. Two fields
were renamed at the jsonb layer: `insurance_provider → insurer`,
`insurance_coverage → insured_value`. `asset_type`, `condition`,
`acquisition_method`, `license_type`, and `status` gained proper
`enum` arrays matching the underlying `physical_assets_*_check` CHECK
constraints — pre-reshape they were loose strings with the enum
hinted in the description (so callers could pass `studio_equipment`
at the MCP layer and get a 500 from the CHECK constraint at the DB
layer).
- **No description lie.** Both tool descriptions now explicitly name
`external_references` as the home for valuation/insurance/DSP URL
details rather than the pre-ADR-174 "tracks … valuation, photos,
insurance, and status" claim that the schema couldn't honour.
### Route-side companion (non-package, same slice)
- `app/api/admin/assets/route.ts` POST and
`app/api/admin/assets/[id]/route.ts` PATCH — phantom/unknown key
rejection with redirect messages (400 + `PHANTOM_FIELD` /
`UNKNOWN_FIELD` error codes). See commit `ced46b889` for the
server-side guard.
## [2.16.0] — 2026-04-15
### Added
- **ADR-173: five release-track tools.** Closes the gap flagged by two
independent external MCP reviewers on 2026-04-14 ("release layer looks
shallow"). New tools on the stdio transport:
- `pica_releases_attach_track` — attach a recording and/or work at a
specific `(disc_number, track_number)`. Idempotent on position.
At least one of `recording_id` / `work_id` is required.
- `pica_releases_list_tracks` — list tracks in `(disc, track)` order
with inlined title/artist/ISRC/duration from the linked recording
and work.
- `pica_releases_detach_track` — soft-confirmation detach by position
(preview without `confirm:true`, delete with).
- `pica_releases_reorder_tracks` — transactional positional reorder
via the `reorder_release_tracks_atomic` Postgres RPC. Rejects
duplicate positions in the payload before any DB write.
- `pica_releases_attach_recording_with_work` — compound workflow that
auto-pulls the recording's linked work onto the new track row.
### Changed
- **`pica_works_inspect` and `pica_recordings_inspect` gain a `releases`
section.** Lazy (only fetched when requested). Returns every release
the entity appears on with its track position. Answers "which albums
is this master on?" in one call — previously unanswerable via MCP.
### Fixed
- **`pica_releases_list_tracks` 500 on every call.** Pre-merge E2E
caught two latent bugs in the `listTracks` service query: a missing
FK `release_tracks.recording_id → recordings(id)` (PostgREST could
not resolve the `recording:recordings(...)` embed) and a
`duration_seconds` column reference where the live column is
`duration_ms`. Fixed in migration
`20260415_01_adr173_fix_release_tracks_recording_fk.sql` +
a rename in `lib/services/releases.ts`. The migration also NULLs 85
orphan `recording_id` values on production left behind by the
2026-04-04 soundslikefez duplicate-works cleanup.
### Note on version number
This release would have been `2.15.0` per the original ADR-173
implementation record, but `2.15.0` was taken on `develop` by the
ADR-174 Phase 2 item 4 slice while the ADR-173 fix commits were in
flight. Bumped to `2.16.0` to keep the published package timeline
linear.
## [2.15.0] — 2026-04-15
### Changed
- **ADR-174 Phase 2 item 4: `pica_people_create` / `pica_people_update`
schema reconciliation.** Introduced `PEOPLE_WRITE_PROPERTIES` as the
canonical source of truth for the people write surface — every
property maps to a real column on either `public.people` or
`public.person_enrichment`. `additionalProperties: true` is off;
unknown keys 400 at the HTTP layer (see develop commit
`fbea9e2a3`).
- **Satellite-routing made explicit.** Eleven fields remain in the
schema but their descriptions now name `person_enrichment` as the
write target: external identifiers (`isni`, `musicbrainz_id`,
`wikidata_id`, `discogs_artist_id`, `deezer_artist_id`) and
biographical data (`date_of_birth`, `gender`, `nationality`,
`birth_place`, `instruments`, `career_start_year`). The service
fans these out through `syncPersonEnrichmentSatellite`. Correction
to the original audit (row 7) landed in ADR-174's Corrections
block: six of these fields were classified as "Remove — no column
on people", which missed the satellite hop. They are not phantom
writes, and subsequent slices must audit satellite write paths
before classifying any field as phantom.
- **Twelve add-candidates surfaced** (audit row 7 "Missing writable
columns"): `middle_name`, `person_type`, `company`, `position`,
`territory`, `social_media`, `emails`, `pro_name`,
`pro_member_number`, `mcps_member_number`, `relationship_strength`,
`email_preferences`. All verified against `public.people` columns
2026-04-15 (project `fwgcmjhlwevdnxgqmkmh`).
- **Deprecated duplicates rejected.** `type` (deprecated dup of
`person_type`) and `role` (deprecated singular of `roles`) are
now 400 PHANTOM_FIELD with a redirect message naming the canonical
column. Both DB columns still exist as unused dead space —
removing them is out of scope and scheduled for ADR-165-style
cleanup.
- **Description lies removed.** The audit (row 8) flagged
`pica_people_update` description as "Additional fields are also
accepted" with no enumeration. That sentence is gone from both
`create` and `update` descriptions.
### Breaking
- Callers that were passing the deprecated `type` / `role`
duplicates now get a 400 with a redirect hint — previously the
keys were silently dropped by the ORM. Same failure-mode calculus
as the works / recordings slices — the writes were already
failing, now they fail visibly.
- Callers that were passing arbitrary unlisted keys now get a 400 —
previously `additionalProperties: true` let them through and the
ORM dropped them silently. Zero rows observed in `people.metadata`
jsonb for any of the pre-existing phantom keys (verified during
ADR-174 Phase 1 2026-04-14).
## [2.14.0] — 2026-04-14
### Changed
- **ADR-174 Phase 2: `pica_recordings_create` / `pica_recordings_update`
schema reconciliation.** Introduced `RECORDING_WRITE_PROPERTIES` as
the canonical source of truth for the recordings write surface —
every property maps to a real column on `public.recordings`. Added
thirteen fields the DB exposes that the tool was hiding:
`duration_ms`, `preview_url`, `album_art_url`, `spotify_url`,
`spotify_track_uri`, `spotify_track_id`, `youtube_video_id`,
`youtube_url`, `apple_music_url`, `deezer_track_id`,
`mlc_recording_id`, `mlc_song_code`, `isrc_prefix`. The
`version_type` field is now enum-restricted to the ADR-166 values
(`master`, `alternate_master`, `music_video`, `lyric_video`,
`live_performance`, `acoustic`, `remix`, `cover`, `alternate`),
matching the `recordings_version_type_check` DB constraint.
- **`pica_recordings_create` default `version_type` changed from
`"original"` to `"master"`.** Pre-fix the executor defaulted to
`"original"`, which is not a valid version_type — every call without
an explicit type tripped the recording-type CHECK constraint and
surfaced as a 500 from the service. `"master"` is the DB-level
default and the canonical ADR-166 value.
- **Title rename made explicit.** `recording_title` is the canonical
column on `public.recordings`. The tool keeps `title` as an input
alias for ergonomics; the schema now declares both fields with the
rename called out in their descriptions, instead of relying on
`additionalProperties: true` to hide the translation.
- Tool descriptions now steer album metadata (`album_name`,
`album_release_date`, `upc`, `track_number`) to
`pica_releases_create/update`, ISWCs to `pica_works_create/update`,
and label metadata to `pica_releases_*.label_organization_id` via
`pica_labels_query`. `duration_seconds` is explicitly named as the
works convention; `recordings` carries `duration_ms`.
### Removed
- **ADR-174 Phase 2: seven phantom fields rejected on
`pica_recordings_create` / `pica_recordings_update`.** The Zod schema
no longer accepts them and `additionalProperties: true` is gone:
`album_name`, `album_release_date`, `upc`, `track_number`, `label`,
`duration_seconds`, `iswc` — none of these corresponded to columns on
`recordings`. The HTTP route at `POST/PATCH /api/admin/recordings[/id]`
now 400s on any of these keys with a redirect message naming the
correct tool (commit `cffa6f947`).
## [2.13.1] — 2026-04-14
### Changed
- **ADR-174 Phase 2: `pica_works_bulk_update.updates` schema gated on
`WORK_WRITE_PROPERTIES`.** The bulk tool was the last write-path
surface that still accepted any key — `updates: { type: "object" }`
with no property list (audit row 25). Now validates against the same
canonical schema as `pica_works_update`: phantom keys (label, upc,
album_name, …) and arbitrary unlisted keys fail fast with a
descriptive `VALIDATION_ERROR` before the SDK is called. One bad key
invalidates the whole batch — no partial writes.
## [2.13.0] — 2026-04-14
### Changed
- **ADR-174 Phase 2: `pica_works_create` / `pica_works_update` schema
reconciliation.** Introduced `WORK_WRITE_PROPERTIES` as the canonical
source of truth for the works write surface — every property maps to
a real column on `public.works`. Added five fields the DB exposes
that the tool was hiding: `tempo`, `key`, `display_artist`,
`published`, `tunecode`. Tool descriptions now steer album/release
metadata (`album_name`, `album_art_url`, `track_number`, `upc`,
`spotify_url`) to `pica_releases_create/update` and label metadata to
`pica_releases_*.label_organization_id` via `pica_labels_query`.
### Removed
- **ADR-174 Phase 2: seven phantom fields removed from
`pica_works_create` / `pica_works_update`.** `label`, `spotify_url`,
`album_name`, `album_release_date`, `album_art_url`, `track_number`,
`upc` — none of these corresponded to columns on `works`. The HTTP
route at `POST/PATCH /api/admin/works[/id]` now 400s on any of these
keys with a redirect message naming the correct tool (commit
`9d68b16b1`). Previously they routed silently into the
`work_album_metadata` / `work_spotify_data` satellites that ADR-165
marked as dead — a write succeeded but no reader could find the
value.
- **ADR-174 Phase 2: `additionalProperties: true` removed from both
works write schemas.** The tool no longer silently accepts arbitrary
keys; unknown keys surface as validation errors. Description lies
about `available_for_licensing`, `vocal_type`, `energy`,
`danceability` also removed — those fields live on satellite tables
and are not exposed through MCP.
## [2.12.0] — 2026-04-14
### Added
- **ADR-174 Phase 2: `pica_labels_query` tool** — read-only resolution of
label names to `organisations.id`. Returns label organisations
(`org_type='label'`) whose name or display_name matches the substring
query, for use as `releases.label_organization_id` via
`pica_releases_create` / `pica_releases_update`. `query` and `limit`
both optional; limit defaults to 50, max 100. Category: catalog.
Annotations: `readOnlyHint: true`.
### Changed
- **ADR-174 Phase 2: `pica_releases_create` / `pica_releases_update`
schema reconciliation.** Input schemas now expose the nine ADR-174
Decision 3 fields that `releases` actually stores:
`label_organization_id`, `distributor_organization_id`,
`pre_release_date`, `artwork_url`, `spotify_album_uri`, `spotify_url`,
`apple_music_url`, `other_urls`, `notes`. Tool descriptions now
direct agents to call `pica_labels_query` (new) or `pica_search_all`
to resolve a label/distributor name to an organisation ID before
writing.
### Removed
- **ADR-174 Phase 2: freetext `label` removed from
`pica_releases_create` / `pica_releases_update`.** The field never
corresponded to a real column on `releases` — the DB models labels
via `label_organization_id` FK. Agents must now resolve a label name
to an organisation ID (via `pica_labels_query`) instead of passing a
string. Pre-fix behaviour: tool schema accepted `label`, service
propagated it to the DB, the DB rejected it as `42703 column does not
exist`, every call 500'd. No working integrations could have relied
on this — the change is strictly a fix, not a break.
## [2.11.0] — 2026-04-13
### Changed
- **ADR-171: Prompts replaced with workflow-level skills.**
11 prompts → 10 skills, each with decision trees, domain knowledge,
enrichment source priority, and the shared preamble (tone, prerequisite
check, summary pattern). Deployed as upgraded prompts; will convert to
MCP skills format when the spec extension ships.
New skills: `workspace-autopilot`, `establish-identity`,
`catalog-your-music`, `upload-and-analyse`, `close-the-loop`,
`manage-collaborators`, `establish-ownership`, `production-assets`,
`stay-connected`, `export-and-report`.
Retired prompts: `analyze-catalog`, `find-duplicates`,
`enrich-metadata`, `verify-works`, `assess-catalog-health`,
`audit-credits`, `new-catalog-setup`, `register-my-works`,
`prepare-for-sync`. Functionality folded into new skills —
see design spec for mapping.
- `workspace-autopilot` now routes through the full journey sequence
(identity → catalog → audio → gaps → collaborators → ownership →
notifications → exports) instead of the old 5-condition router.
- `close-the-loop` absorbs 7 former prompts and adds the enrichment
cascade decision tree (MLC first if IPI, MusicBrainz second, Spotify
third) and inline duplicate detection.
- `export-and-report` absorbs `analyze-catalog`, `register-my-works`,
and `prepare-for-sync` into a single export/report skill with CAR,
CWR, CSV, provenance, sync readiness, and carbon footprint paths.
## [2.10.0] — 2026-04-11
### Added
- **ADR-163: Tier B enrichment proposal review tools.**
- `pica_enrichment_proposals_list` — list pending proposals
awaiting review. Filters: `entity_type`, `entity_id`,
`rule_id`, `source`, `limit` (1..100, default 20), `offset`.
Only `status='pending'` rows are returned. Backed by
`GET /api/admin/enrichment-proposals`.
- `pica_enrichment_proposal_apply` — apply a pending proposal.
For update proposals, runs drift detection first: if the
entity has changed since the proposal was created, returns
`{ status: 'drift_detected', conflicts }` without writing.
Agents surface conflicts to the user before retrying with
`force=true`. For create proposals (`artist_title_to_youtube`),
a uniqueness check runs instead and cannot be forced. Backed
by `POST /api/admin/enrichment-proposals/:id/apply`.
- `pica_enrichment_proposal_reject` — reject a pending proposal
with an optional `resolution_note`. Content-hash suppression
(ADR-163) permanently blocks re-proposal of identical content
— no cooldown, no timer. Backed by
`POST /api/admin/enrichment-proposals/:id/reject`.
- **ADR-166: YouTube import as a recording-version source.**
- `pica_import_youtube_link` MCP tool wraps the Phase 3 orchestrator.
Takes a YouTube URL, returns a preview (items + classification +
work match), then `confirm: true` executes the import as a new
recording on an existing or newly-created work. Supports
`selectedVideoIds`, `targetWorkId`, and `overrideVersionType`
for fine-grained agent control. Landed in `78da58344`
(feat(adr-166): phase 4 — pica_import_youtube_link MCP tool + SDK
methods).
### Changed
- `pica_cascade_health` outcome bucket list extended to include
the ADR-163 Phase 1 `proposed` and `suppressed` outcomes. Tool
description now reads `wrote / no_match / already_enriched /
error / proposed / suppressed`. Counters populate automatically
from `cascadeOutcomeCounter` — no schema change required for
existing `/api/admin/cascade-health` or
`/api/internal/cascade-health` consumers.
- Several mcp-server source files got minor reformatting from ADR-165's
flattener strip (removing `work_spotify_data` + `work_album_metadata`
fields from the Work shape) and ADR-166's cascade rule rewrites.
These are consumer-invisible at the MCP tool surface — the tools
still accept the same inputs and return the same outputs — but the
underlying `PicaClient.works` reads + writes now route through the
ADR-165 overlay. See the ADR-165 and ADR-166 implementation records
for details.
### Fixed
- `pica_run_person_cascade` tool description had a dangling
`→ then: pica_people_inspect` chain with no parenthetical reason hint,
which the `composability-chains.test.ts` linter caught as a content
bug. Description updated to `→ then: pica_people_inspect (see what
the cascade wrote)`. Fixed in `3a2843636` (test(mcp-server): fix 10
pre-existing test failures across 8 suites).
### Notes
- Backwards-compatible minor bump. Three new tools, one widened
description, no removals or signature changes.
- Depends on `@withpica/mcp-sdk@^1.4.0` for the new
`EnrichmentResource.listEnrichmentProposals` /
`applyEnrichmentProposal` / `rejectEnrichmentProposal` methods.
- Source additions landed across ADR-163 Phase 3d (`ff9cf265a`) and
Phase 6 (`ff97faa56`).
- Registry: https://www.npmjs.com/package/@withpica/mcp-server/v/2.10.0
- Published alongside `@withpica/mcp-sdk@1.4.0` and
`@withpica/mcp-server-business@1.3.0` in ADR-163 Phase 6.
## [2.9.0] — 2026-04-11
### Added
- **ADR-164 audit follow-up: three new cascade MCP tools**
- `pica_run_work_cascade(work_id)` — manually re-evaluate a work
against every ADR-164 cascade rule and fire those whose
preconditions are satisfied. Idempotent. Fire-and-forget. Backed by
`POST /api/admin/works/:id/cascade-run`.
- `pica_run_person_cascade(person_id)` — same for people. Fires
`ipi_to_mlc_works` (MLC writer/publisher splits),
`mbid_to_wikidata_crosswalk`, `isni_to_wikidata_crosswalk`,
`wikidata_to_awards`, `wikidata_to_biography`, and
`google_contact_suggest`. Backed by
`POST /api/admin/people/:id/cascade-run`.
- `pica_cascade_health` — snapshot of in-process cascade counters
(ADR-164 Decision 5). Org-auth twin of the operator-only
`/api/internal/cascade-health` endpoint. Returns per-rule failure
counts bucketed by reason + outcome totals. Process-global, not
persistent across serverless cold starts — treat as a spot-check.
### Notes
- Source additions landed in `38901f281` (ADR-164 audit follow-ups).
Version bump + rebuilt dist + publish tagged in `edfbcb07b`.
- Minor bump per semver — new tools, no removed or renamed surface.
- Registry URL:
https://www.npmjs.com/package/@withpica/mcp-server/v/2.9.0
- Tarball verified post-publish by pulling
`withpica-mcp-server-2.9.0.tgz` from the registry and grepping for
all three new tool names in `package/dist/tools/enrichment.js`.
## [2.8.0] — 2026-04-10
### Changed
- **ADR-162: subscription-based billing, credit gating disabled.**
`withCreditGate` is now a pass-through — all tools are free for
active subscribers. The MPP (Micropayment Protocol) machinery is
retained in `@withpica/mcp-utils` for a future pay-per-credit
re-enable but is dormant. Version bump carried in `7c4f5d24a`
(`chore(adr-162): version bumps for npm publish`).
## [2.7.0] — 2026-04-08
### Added
- **ADR-158c custody tool release.** Six `pica_custody_*` tools go
live: claim, accept, decline, dispute, incoming list, history.
Version bump in `91dd1c275` (`chore(adr-158c): version bumps for
custody tool release (Phase 6b)`).
## [2.6.2] — pre-2026-04-08
### Fixed
- Stale version constant in the boot banner — the banner was still
reporting an older version after an earlier bump. Fixed in
`980dabc02` (`fix(mcp-server): correct stale version constant in
boot banner (2.6.2)`).
## [2.6.1] — pre-2026-04-08
### Fixed
- MCP server now emits `tools/list_changed` + `resources/list_changed`
notifications after sign-in and sign-out, so clients refresh the
tool list without a reconnect. Fixed in `f1ba2a211`.
## [2.6.0] — 2026-04-07
### Changed
- **ADR-155 launch — publish 6 packages.** Catalog server rebaselined
from the broken `3.0.0` publish (deprecated + unpublished from npm
because `file:` deps had been baked into the tarball, leaving every
fresh installer dead) to `2.6.0`. Five new sibling packages
(`@withpica/mcp-sdk`, `@withpica/mcp-utils`,
`@withpica/mcp-server-business`, `-enrichment`, `-settings`)
published at `1.0.0` alongside with real semver deps so `npm
install` actually resolves on user machines.
- `connect.withpica.com` leads with the no-api-key lobby-mode flow.
Landed in `e235aed40` (`feat(mcp): ADR-155 launch — publish 6
packages, lobby-mode connect copy`).
## Pre-2.6.0 — pre-changelog era
Earlier versions (`2.0.0` through `2.5.x`) covered the pre-ADR-155
monolithic MCP server. Version history for that era is reconstructable
via `git log --follow -- mcp-server/package.json`. Notable milestones:
- `2.5.x` — MCP Apps prebuild script + ext-apps devDependency added
- `2.4.x` — build/publish polish, `.vercel/` excluded from tarball
- `~2.0.0` — ADR-149 agentic hardening (write tiers, ProposedAction,
confirmation, audit, annotations)
[Unreleased]: https://github.com/withpica/pica/compare/edfbcb07b...HEAD
[2.9.0]: https://github.com/withpica/pica/compare/7c4f5d24a...edfbcb07b
[2.8.0]: https://github.com/withpica/pica/compare/91dd1c275...7c4f5d24a
[2.7.0]: https://github.com/withpica/pica/compare/980dabc02...91dd1c275
[2.6.2]: https://github.com/withpica/pica/compare/f1ba2a211...980dabc02
[2.6.1]: https://github.com/withpica/pica/compare/e235aed40...f1ba2a211
[2.6.0]: https://github.com/withpica/pica/commit/e235aed40