@withpica/mcp-server
Version:
MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog
177 lines • 8.33 kB
JavaScript
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved.
/**
* The one alias registry. Populated per-consolidation; EMPTY as of
* mcp-server 2.99.0.
*
* REMOVAL (mcp-server 2.99.0, founder decision 2026-09-29): the last eleven
* aliases were removed — their entries here, their TOOL_METADATA mirrors,
* and any standalone ToolDefinitions that still sat beside the canonical
* tools. Prod `agent_events` over the 60 days to 2026-09-29: 4 calls in all
* (pica_recording_credits_bulk_update 2, last 08-17; pica_releases_get 1,
* 08-01; pica_royalties_summary 1, 09-14; the other eight zero). Calling any
* of them now gets TOOL_NOT_FOUND whose message names its replacement; the
* replacement also leads `did_you_mean` when the caller can call it. The
* name → replacement mapping is RETIRED_TOOLS below — the one copy.
* The dated notes below are the history of how that deadline moved.
*
* REMOVAL SWEEP (mcp-server 2.93.0, 2026-07-12): the first batch of WS5
* deprecated names were traffic-gated against prod `agent_events` (30-day
* window). Six identity read-fold aliases with ZERO recent traffic were
* DELETED entirely this release — their alias entry here, their standalone
* ToolDefinition, TOOL_METADATA, cost-model, and required-schemas companion
* rows all went with them: `pica_projects_get`, `pica_notes_get`,
* `pica_sessions_list`, `pica_sessions_get`, `pica_statements_get`,
* `pica_share_links_list`. The ten aliases that then remained still carried
* real live traffic, so they STAYED callable — their `removal_target` was
* consciously extended (the "expire loudly" lint would otherwise have blocked
* the 2.93.0 publish), with the traffic check to be re-run before the new
* target. They were finally removed in 2.99.0 (above).
*
* ⚠️ `removal_target` HERE IS DOCUMENTATION, NOT THE GATE (noted 2026-08-13).
* `checkDeprecationExpiry` in `scripts/lint-mcp-tools.ts` reads the value from
* TOOL_METADATA in `metadata.ts`, never this file — so if the two disagree it
* is metadata's value that expires the alias.
*
* They cannot silently disagree any more: `checkAliasMetadataMirror`
* (`scripts/lint-mcp-tools.ts`) is handed this file's targets by
* `loadAliasRemovalTargets` and hard-fails on any mismatch, not merely on a
* missing mirror. That comparison was ADDED because they HAD drifted: this
* file said 2.95.0 while metadata said 2.96.0, and the package shipped 2.95.2
* without the lint firing. Realigned to 2.96.0, then DEFERRED to
* 2.97.0 on 2026-08-26 — the MPP pay rail (WS-B) needed a minor bump for a new
* tool, the next minor after 2.95.4 is unavoidably 2.96.0, and none of these
* ten aliases had been removed. See the matching comment in `metadata.ts`. If
* you change a target, change BOTH — or the comment stops describing the
* deadline the build actually enforces.
*
* ⚠️ Deferred 2026-09-04: 2.97.0 became the ops-root-causes release
* (aliases, envelopes, offsets — PR fix/ops-root-causes-2026-09-03).
* Alias removal needs its own PR with an agent_events usage check per
* alias (SELECT tool_name, count(*) FROM agent_events WHERE tool_name IN
* (…) AND created_at > now() - interval '60 days'). Founder item F10
* schedules it before 2.98.0; deferred again 2026-09-29 to 2.99.0 (founder:
* 2.98.0 shipped the ADR-321 Phase 2 release without removals).
*/
export const TOOL_ALIASES = [
// Empty since mcp-server 2.99.0 — every alias has been removed (history in
// the comment above). Add a new entry here, with its TOOL_METADATA mirror,
// for the next deprecation.
];
export const RETIRED_TOOLS = {
// mcp-server 2.99.0 (founder, 2026-09-29) — the last eleven aliases.
pica_releases_list: {
replacement: "pica_releases_query",
removed_in: "2.99.0",
},
pica_releases_get: {
replacement: "pica_releases_inspect",
removed_in: "2.99.0",
},
pica_releases_bulk_attach_tracks: {
replacement: "pica_releases_attach_track",
removed_in: "2.99.0",
argument_change: "pass the tracks as items[] (the old tracks field)",
},
pica_releases_attach_track_by_identifier: {
replacement: "pica_releases_attach_track",
removed_in: "2.99.0",
argument_change: "pass identifier_type + value (with track_number) on pica_releases_attach_track",
},
pica_projects_list: {
replacement: "pica_projects_query",
removed_in: "2.99.0",
},
pica_notes_list: { replacement: "pica_notes_query", removed_in: "2.99.0" },
pica_documents_list: {
replacement: "pica_documents_query",
removed_in: "2.99.0",
},
pica_documents_read: {
replacement: "pica_documents_inspect",
removed_in: "2.99.0",
},
pica_statements_list: {
replacement: "pica_statements_query",
removed_in: "2.99.0",
},
pica_royalties_summary: {
replacement: "pica_royalties_blockers",
removed_in: "2.99.0",
argument_change: "work_id and limit carry over; gap_type and priority have no equivalent",
},
pica_recording_credits_bulk_update: {
replacement: "pica_recording_credits_update",
removed_in: "2.99.0",
argument_change: "pass the credit set as a credits[] array (with release_id or recording_ids)",
},
};
/** The retired-tool record for a name, if it is one. */
export function getRetiredTool(name) {
return Object.prototype.hasOwnProperty.call(RETIRED_TOOLS, name)
? RETIRED_TOOLS[name]
: undefined;
}
/**
* Adjust a not-found answer for a retired name. Pure.
*
* `similar` is the similarity-ranked suggestion list the caller already built
* (at most three names). For a retired name whose replacement `canCall`
* accepts, the replacement goes FIRST and the list stays at most three long;
* `note` is the sentence to lead the message with. A replacement the caller
* cannot call is named in `note` (the mapping is published in the package
* CHANGELOG) but never added to the suggestions. This function does not
* filter `similar` itself: each caller builds it from names the caller can
* call (`canSuggest` in discovery.ts; the `allowedToolNames` pool in
* app/api/mcp/tool-not-found.ts). For any other name the list comes back
* unchanged and `note` is null.
*/
export function retiredToolAnswer(name, similar, canCall) {
const retired = getRetiredTool(name);
if (!retired)
return { suggestions: [...similar], note: null };
const suggestions = canCall(retired.replacement)
? [
retired.replacement,
...similar.filter((n) => n !== retired.replacement),
].slice(0, 3)
: [...similar];
const note = `${name} was retired in mcp-server ${retired.removed_in}; use ` +
`${retired.replacement} instead` +
(retired.argument_change ? ` — ${retired.argument_change}` : "") +
".";
return { suggestions, note };
}
/** True when a tool's metadata marks it deprecated (aliased). Pure. */
export function isDeprecatedMeta(meta) {
return meta?.deprecated === true;
}
/** Look up the alias record for a deprecated name, if any. */
export function getAliasFor(name) {
return TOOL_ALIASES.find((a) => a.deprecated === name);
}
/**
* For every alias whose canonical is present in `registered`, register the
* deprecated name as a hidden, callable clone that delegates to the canonical.
* Aliases with a missing canonical (e.g. lobby mode) are skipped silently.
*/
export function expandAliases(registered, aliases) {
for (const alias of aliases) {
const canonical = registered.get(alias.canonical);
if (!canonical)
continue;
const canonicalExecutor = canonical.executor;
const def = {
...canonical.definition,
name: alias.deprecated,
tier: canonical.definition.tier,
scope: canonical.definition.scope,
audiences: canonical.definition.audiences,
inputSchema: canonical.definition.inputSchema,
};
registered.set(alias.deprecated, {
definition: def,
executor: (args, ctx) => canonicalExecutor(alias.adaptArgs ? alias.adaptArgs(args) : args, ctx),
});
}
}
//# sourceMappingURL=deprecations.js.map