🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@labelgrid/mcp

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@labelgrid/mcp - npm Package Compare versions

Comparing version
0.5.0
to
0.6.0
+36
-0
CHANGELOG.md

@@ -8,2 +8,38 @@ # Changelog

## [0.6.0] - 2026-08-05
### Added
- `get_analytics_rankings` — top-N rankings for a window, ordered by summed
streams. `view: 'leaderboards'` ranks top artists, tracks or albums (`type`
required); `view: 'placements'` ranks the playlists and radio containers
driving streams.
- `get_rate_limit` — the account's API rate budget per category, with the
ceiling, the remaining allowance and the window reset.
- 10 new analytics section keys (47 total): eight social and UGC sections
(`social-usage-over-time`, `social-reach-over-time`, `social-platform-mix`,
`social-top-tracks`, `social-territory`, `social-artist-reach`,
`social-artist-reach-daily`, `soundcloud-engagement`) and two per-track daily
series (`track-streams-daily`, `track-listeners-daily`, which need a
`release_id`, `isrc` or `upc` scope).
- `ugc_platform` filter on `get_analytics`, narrowing the social and UGC
sections.
- `AMAZON` as a `platform` filter value on `get_analytics` and
`get_analytics_rankings` (11 total), matching the platforms the API accepts.
- `query_financials` concise mode now returns the fee fields — `labelgrid_fee`,
`platform_fee_usd`, `ugc_fee_usd`, `labelgrid_rate` and `labelgrid_ugc_rate`.
Previously a fee was visible only with `response_format: 'detailed'`.
### Changed
- Social and UGC sections report their own per-section availability in
`meta.social_availability`; the streaming availability matrix does not cover
them. A use, a view and a play are distinct quantities and are never summed
with each other or with streams.
- `track-listeners-daily` sums each platform track entry's daily count. Where a
track exists as more than one entry it is not a distinct count of people, and
it is not summable across dates.
- Tool descriptions across the catalog were tightened. No tool names,
parameters or behavior changed beyond the items above.
## [0.5.0] - 2026-07-27

@@ -10,0 +46,0 @@

+3
-0

@@ -20,2 +20,3 @@ /**

'GET /me': 'get_account',
'GET /rate-limit': 'get_rate_limit',
'DELETE /tokens/current': 'revoke_api_token',

@@ -34,2 +35,4 @@ 'DELETE /tokens/{tokenId}': 'revoke_api_token',

'GET /analytics/availability': 'get_analytics_availability',
'GET /analytics/leaderboards': 'get_analytics_rankings',
'GET /analytics/placements': 'get_analytics_rankings',
// catalog reads

@@ -36,0 +39,0 @@ 'GET /labels': 'search_catalog',

@@ -75,2 +75,7 @@ /**

'gross_usd',
'labelgrid_fee',
'platform_fee_usd',
'ugc_fee_usd',
'labelgrid_rate',
'labelgrid_ugc_rate',
'net_usd',

@@ -77,0 +82,0 @@ 'amount',

+4
-1

@@ -1,3 +0,6 @@

/** Account toolset: read the authenticated account, revoke API tokens. */
/**
* Account toolset: read the authenticated account, read its rate budget, revoke
* API tokens.
*/
import type { ToolDef } from './types.js';
export declare const accountTools: ToolDef[];

@@ -1,2 +0,5 @@

/** Account toolset: read the authenticated account, revoke API tokens. */
/**
* Account toolset: read the authenticated account, read its rate budget, revoke
* API tokens.
*/
import { z } from 'zod';

@@ -17,2 +20,13 @@ const getAccount = {

};
const getRateLimit = {
name: 'get_rate_limit',
toolset: 'account',
gate: 'read',
title: 'Get the rate budget',
description: 'The account’s API rate budget per category (read, write, export, analytics): ceiling, remaining, and window reset. ' +
'Every token on the account shares one budget; a null ceiling means none applies. Free to call — use it to pace requests and to recover from a 429.',
inputShape: {},
annotations: { readOnlyHint: true },
handler: (_args, { client }) => client.get('/rate-limit'),
};
const revokeApiToken = {

@@ -33,2 +47,2 @@ name: 'revoke_api_token',

};
export const accountTools = [getAccount, revokeApiToken];
export const accountTools = [getAccount, getRateLimit, revokeApiToken];

@@ -24,3 +24,3 @@ /**

.optional()
.describe("'concise' (default) keeps high-signal fields (ids always kept); 'detailed' returns the verbatim API response.");
.describe("'concise' (default) keeps high-signal fields and ids; 'detailed' is the verbatim response.");
/** A permissive body of API fields, forwarded verbatim to the endpoint. */

@@ -208,3 +208,3 @@ function fieldsBody(desc) {

"(3) mode='download_url' + parent='track' + asset audio_16|audio_24|audio_32 (WAV master) or audio_preview_full|audio_preview_clip (MP3 preview) — returns { download_url, expires_in }, a signed URL that expires roughly 10 minutes after issue; fetch it directly — do not send your API token to it. " +
'Any other combination returns a structured error.',
'Any other combination is refused.',
inputShape: {

@@ -226,3 +226,3 @@ parent: z.enum(['track', 'release']).describe('Whose asset.'),

])
.describe('Which asset — see the description for the valid parent/mode pairings.'),
.describe('Which asset — see the valid combinations above.'),
mode: z

@@ -229,0 +229,0 @@ .enum(['info', 'download_url'])

@@ -49,4 +49,4 @@ /**

.describe('Where the license came from.'),
license_provider_name: z.string().optional().describe('The name of the license provider.'),
original_track_link: z.string().optional().describe('URL to the original/source track.'),
license_provider_name: z.string().optional(),
original_track_link: z.string().optional().describe('URL to the source track.'),
};

@@ -74,6 +74,6 @@ /** Collects the defined license metadata fields into a string map for multipart. */

title: 'Upload a release/track asset',
description: 'Upload a finalized track or release asset from a local file. `id` is the track id for track_* targets, the release id for release_*. ' +
description: 'Upload a finalized track or release asset from a local file. ' +
'`track_stereo` (WAV/FLAC/AIFF), `track_dolby` (Dolby Atmos WAV) and `track_lyrics` (LRC) process asynchronously — check state with get_asset (mode info). ' +
'`release_cover_art` uploads or replaces the static cover art image. ' +
'`release_motion_square` / `release_motion_tall` upload the square or tall animated cover (motion artwork) video, also asynchronous. ' +
'`release_motion_square`/`release_motion_tall` upload the animated cover (motion artwork) video, also asynchronous. ' +
'All assets become immutable once the release is distributed — upload final files first.',

@@ -91,3 +91,3 @@ inputShape: {

.describe('Which asset to upload.'),
id: z.number().int().positive().describe('The track id (track_*) or release id (release_*).'),
id: z.number().int().positive().describe('The track or release id, per `target`.'),
file_path: z.string().describe('Local path to the file to upload.'),

@@ -176,3 +176,3 @@ },

.optional()
.describe('From list_track_licenses. Required for update/delete.'),
.describe('Required for update/delete.'),
file_path: z

@@ -179,0 +179,0 @@ .string()

@@ -298,3 +298,3 @@ /**

'`statement_detail` retrieves one statement by `invoice_number` (required). ' +
'`transactions` lists account transactions, paginated — same `filters`; sort with `sort`; group_by="release" rolls up per release. ' +
'`transactions` lists account transactions, paginated — same `filters` and `group_by`; sort with `sort`. ' +
'`royalty_breakdown` returns a cursor-paginated royalty breakdown — `group_by` is REQUIRED for this view: a comma-separated, ordered subset of: track, dsp, release, territory, period (e.g. "release,dsp"); same `filters`; pass `cursor` to page. ' +

@@ -310,3 +310,3 @@ "Use download_statement for statement line items (CSV) or the invoice PDF. response_format:'detailed' returns the verbatim API response.",

.optional()
.describe('REQUIRED for royalty_breakdown (ordered subset: track, dsp, release, territory, period); "release" rolls statements/transactions up per release.'),
.describe('Required for royalty_breakdown; "release" rolls statements/transactions up per release.'),
sort: z.string().optional().describe('Sort expression (view transactions).'),

@@ -316,3 +316,3 @@ filters: z

.optional()
.describe('label_id, release_id, isrc, upc, start_date, end_date — passed through verbatim.'),
.describe('Filter names → values, passed through verbatim.'),
cursor: z.string().optional().describe('Pagination cursor (view royalty_breakdown).'),

@@ -324,3 +324,3 @@ page: z.number().int().positive().optional().describe('1-based page number.'),

.optional()
.describe("'concise' (default) keeps only the high-signal fields (ids always kept); 'detailed' returns the verbatim API response."),
.describe("'concise' (default) or 'detailed'."),
},

@@ -376,7 +376,5 @@ annotations: { readOnlyHint: true },

title: 'Download a statement file',
description: "Download statement files. `format: 'csv'` downloads statement line items — pass invoice_number for one statement, OR a start_date/end_date range to export across statements; with save_to_path (an absolute path whose parent directory exists) the CSV is written there and the byte count returned; otherwise it is returned inline, truncated at 100KB (truncated: true) — use save_to_path for large exports. `format: 'invoice_pdf'` downloads the invoice PDF — invoice_number and save_to_path are both REQUIRED (the PDF is binary). An existing file is never overwritten (returns FILE_EXISTS).",
description: "Download statement files. `format: 'csv'` downloads statement line items — pass invoice_number for one statement, OR a start_date/end_date range to export across statements; with save_to_path the CSV is written there and the byte count returned; otherwise it is returned inline, truncated at 100KB (truncated: true) — use save_to_path for large exports. `format: 'invoice_pdf'` downloads the invoice PDF — invoice_number and save_to_path are both REQUIRED (the PDF is binary). An existing file is never overwritten (returns FILE_EXISTS).",
inputShape: {
format: z
.enum(['csv', 'invoice_pdf'])
.describe('Which file: csv (line items) or invoice_pdf (the invoice PDF).'),
format: z.enum(['csv', 'invoice_pdf']).describe('Which file.'),
invoice_number: z

@@ -391,3 +389,3 @@ .string()

.optional()
.describe('Absolute path (existing parent dir) to write the file to. Optional for csv (otherwise returned inline); required for invoice_pdf.'),
.describe('Absolute path (its parent directory must exist) to write the file to.'),
},

@@ -394,0 +392,0 @@ annotations: { readOnlyHint: true },

/**
* Insights toolset: the streaming analytics summary, the availability-discovery
* endpoint, and the consolidated artificial-streaming query (early-warning
* flags, reported records, and the fee breakdown). All read-only.
* endpoint, the top-N rankings (leaderboards and placements), and the
* consolidated artificial-streaming query (early-warning flags, reported
* records, and the fee breakdown). All read-only.
*/
import type { ToolDef } from './types.js';
export declare const insightsTools: ToolDef[];
/**
* Insights toolset: the streaming analytics summary, the availability-discovery
* endpoint, and the consolidated artificial-streaming query (early-warning
* flags, reported records, and the fee breakdown). All read-only.
* endpoint, the top-N rankings (leaderboards and placements), and the
* consolidated artificial-streaming query (early-warning flags, reported
* records, and the fee breakdown). All read-only.
*/

@@ -9,4 +10,5 @@ import { z } from 'zod';

/**
* The 37 metric sections the summary endpoint can return, in the server's
* canonical order.
* The 47 metric sections the summary endpoint can return, in the server's
* canonical order: the streaming sections first, then the social and UGC
* family, then the per-track daily series.
*/

@@ -51,3 +53,28 @@ const METRICS = [

'shazams-by-state',
'social-usage-over-time',
'social-reach-over-time',
'social-platform-mix',
'social-top-tracks',
'social-territory',
'social-artist-reach',
'social-artist-reach-daily',
'soundcloud-engagement',
'track-streams-daily',
'track-listeners-daily',
];
/**
* The UGC platform values `filter[ugc_platform]` accepts. A separate axis from
* `platform`: it narrows the social and UGC sections only, and its values never
* appear in a streaming total or platform share.
*/
const UGC_PLATFORMS = [
'snapchat',
'instagram',
'facebook',
'soundcloud',
'tiktok',
'whatsapp',
'threads',
'messenger',
];
/** The maximum number of section keys the server accepts per summary request. */

@@ -64,2 +91,3 @@ const MAX_METRICS_PER_REQUEST = 12;

'AUDIOMACK',
'AMAZON',
'KUGOU',

@@ -73,10 +101,12 @@ 'KUWO',

gate: 'read',
title: 'Get streaming analytics',
title: 'Get streaming and social analytics',
description: 'Streaming analytics summary. Window capped at 400 days; `metrics` takes 1-12 section keys per request (split larger selections — responses are cached). ' +
'KUGOU/KUWO/QQMUSIC report weekly: one point per week carrying the whole week — never average it per day. `meta` carries `platform_cadence`, `section_granularity`, `sections_as_of` and `sections_complete_through` (later dates still filling in). ' +
'Call get_analytics_availability first for section-per-platform support. ' +
'The `social-*` / `soundcloud-engagement` sections cover social and UGC usage instead of streaming: their `platform` is a UGC platform; a use, view and play are distinct quantities, never summed with each other or with streams; `ugc_platform` narrows them. Selecting any adds `meta.social_availability` (which UGC platforms report each signal) — the streaming matrix excludes them. ' +
'The `track-*-daily` sections need a `release_id`, `isrc` or `upc` scope. `track-listeners-daily` sums per-entry daily counts: not distinct people, not summable across dates. ' +
'Rate-limited ~60/min; windows over 90 days draw a separate lower ~30/min budget — prefer shorter windows for polling. A 429 carries retry_after_seconds.',
inputShape: {
start_date: z.string().describe('Start of the reporting window, YYYY-MM-DD.'),
end_date: z.string().describe('End of the reporting window, YYYY-MM-DD (max 400-day span).'),
start_date: z.string().describe('Window start, YYYY-MM-DD.'),
end_date: z.string().describe('Window end, YYYY-MM-DD.'),
metrics: z

@@ -86,8 +116,12 @@ .array(z.enum(METRICS))

.max(MAX_METRICS_PER_REQUEST)
.describe('Section keys to return, 1-12 per request.'),
.describe('Section keys, 1-12 per request.'),
platform: z.enum(PLATFORMS).optional(),
ugc_platform: z
.enum(UGC_PLATFORMS)
.optional()
.describe('Narrows the social/UGC sections only.'),
release_id: z.number().int().positive().optional(),
isrc: z.string().optional(),
upc: z.string().optional(),
artist_names: z.array(z.string()).optional().describe('Filter to one or more artist names.'),
artist_names: z.array(z.string()).optional(),
limit: z.number().int().positive().optional(),

@@ -101,2 +135,3 @@ },

platform: args.platform,
ugc_platform: args.ugc_platform,
release_id: args.release_id,

@@ -122,2 +157,65 @@ isrc: args.isrc,

};
/** The two ranking reads, and the entity kinds a leaderboard can rank. */
const RANKING_VIEWS = ['leaderboards', 'placements'];
const LEADERBOARD_TYPES = ['artists', 'tracks', 'albums', 'all'];
/** Upper bound the ranking endpoints place on `limit`. */
const MAX_RANKING_LIMIT = 50;
const getAnalyticsRankings = {
name: 'get_analytics_rankings',
toolset: 'insights',
gate: 'read',
title: 'Get analytics rankings',
description: 'Top-N rankings for a window, ordered by summed streams. Pick ONE `view`: ' +
'`leaderboards` — your top artists, tracks or albums (`type` required; `all` returns all three in one request). ' +
'`placements` — the playlists and radio containers driving streams, summed across storefronts. ' +
'Same scope filters as get_analytics; `limit` 1-50 (default 10). Under a `platform` filter, an `availability` of `not_available_for_platform` means that platform reports no ranking and `data` is empty.',
inputShape: {
view: z.enum(RANKING_VIEWS).describe('Which ranking read.'),
start_date: z.string().describe('Window start, YYYY-MM-DD.'),
end_date: z.string().describe('Window end, YYYY-MM-DD.'),
type: z.enum(LEADERBOARD_TYPES).optional().describe('Required for view leaderboards.'),
platform: z.enum(PLATFORMS).optional(),
ugc_platform: z.enum(UGC_PLATFORMS).optional(),
release_id: z.number().int().positive().optional(),
isrc: z.string().optional(),
upc: z.string().optional(),
artist_names: z.array(z.string()).optional(),
label_id: z
.number()
.int()
.positive()
.optional()
.describe('Narrow to one of your own labels; it can never widen scope.'),
limit: z.number().int().positive().max(MAX_RANKING_LIMIT).optional(),
},
annotations: { readOnlyHint: true },
handler: (args, { client }) => {
const leaderboards = args.view === 'leaderboards';
if (leaderboards && args.type === undefined) {
return Promise.resolve({
error: {
code: 'INVALID_SELECTOR',
message: "view 'leaderboards' requires `type` — artists, tracks, albums, or all. `type` does not apply to view 'placements'.",
status: 0,
},
});
}
return client.get(leaderboards ? '/analytics/leaderboards' : '/analytics/placements', {
filter: {
start_date: args.start_date,
end_date: args.end_date,
platform: args.platform,
ugc_platform: args.ugc_platform,
release_id: args.release_id,
isrc: args.isrc,
upc: args.upc,
artist_names: args.artist_names,
label_id: args.label_id,
},
// `type` is a leaderboards-only parameter — never sent to placements.
type: leaderboards ? args.type : undefined,
limit: args.limit,
});
},
};
const queryArtificialStreaming = {

@@ -150,3 +248,3 @@ name: 'query_artificial_streaming',

.optional()
.describe("'concise' (default) keeps only the high-signal fields (ids always kept); 'detailed' returns the verbatim API response."),
.describe("'concise' (default) or 'detailed'."),
},

@@ -202,3 +300,4 @@ annotations: { readOnlyHint: true },

getAnalyticsAvailability,
getAnalyticsRankings,
queryArtificialStreaming,
];

@@ -10,7 +10,6 @@ /** Reference toolset: one tool serving all read-only lookup datasets. */

description: 'Fetch a LabelGrid reference dataset used to resolve the IDs and codes the catalog and release tools expect. Pick ONE dataset with `type`: ' +
'`genres` and `genre_categories` (genre IDs), `languages` (audio/metadata language codes), `contributor_roles`, `instruments`, `distro_outlets` (the outlets/stores available to your account), ' +
'`territories` (country codes), `issue_definitions` (each review issue code’s title, description, severity and whether it blocks distribution; codes are string slugs), ' +
'or `webhook_event_types` (every webhook event type with its payload schema). ' +
'Call this when you need a valid ID or code. ' +
'The same datasets are exposed as MCP resources at labelgrid://reference/{type}; this tool is the fallback for clients that don’t surface resources.',
'`genres` and `genre_categories` (genre IDs), `languages` (audio/metadata language codes), `contributor_roles`, `instruments`, `distro_outlets` (the outlets/stores available to you), ' +
'`territories` (country codes), `issue_definitions` (review issue codes — string slugs — with severity and whether they block distribution), ' +
'or `webhook_event_types` (event types with their payload schemas). ' +
'Also exposed as MCP resources at labelgrid://reference/{type}; this tool is the fallback.',
inputShape: {

@@ -17,0 +16,0 @@ type: z.enum(REFERENCE_TYPES),

@@ -16,3 +16,3 @@ /**

.optional()
.describe("'concise' (default) keeps only the high-signal fields (ids always kept); 'detailed' returns the verbatim API response.");
.describe("'concise' (default) keeps high-signal fields and ids; 'detailed' is the verbatim response.");
const getReleaseReview = {

@@ -19,0 +19,0 @@ name: 'get_release_review',

@@ -13,10 +13,7 @@ /**

title: 'List webhooks',
description: "Read your webhook subscriptions. `view: 'config'` (the default) lists the webhook subscriptions configured on your account — each with its URL, subscribed events and active state — or retrieves one subscription when `webhook_id` is given. " +
description: "Read your webhook subscriptions. `view: 'config'` (the default) lists them — URL, subscribed events, active state — or retrieves one when `webhook_id` is given. " +
"`view: 'logs'` retrieves the recent delivery log for a webhook (`webhook_id` required) — attempts, response codes and outcomes — to debug why events did or did not reach your endpoint.",
inputShape: {
webhook_id: webhookId,
view: z
.enum(['config', 'logs'])
.optional()
.describe('config (default) reads subscriptions; logs reads a webhook’s delivery log.'),
view: z.enum(['config', 'logs']).optional().describe('config (default) or logs.'),
},

@@ -50,7 +47,7 @@ annotations: { readOnlyHint: true },

description: 'Manage a webhook subscription. Pick ONE action with `action`: ' +
'`create` — pass `fields` with `name`, `url` (the HTTPS endpoint receiving deliveries) and `events` (the event subscription object — see list_reference_data type webhook_event_types); the API returns a signing secret ONCE on creation — store it to verify incoming payloads. ' +
'`create` — `fields`: `name`, `url` (the HTTPS endpoint receiving deliveries), `events` (see list_reference_data type webhook_event_types); the signing secret is returned ONCE on creation — store it to verify payloads. ' +
'`update` — supply only the fields to change in `fields`: name, url, events, or is_active (false pauses deliveries). ' +
'`delete` — permanently removes the subscription; it stops receiving events. ' +
'`test` — sends a test event to confirm reachability and signature verification; safe to repeat. ' +
'`rotate_secret` — generates and returns a new signing secret — WARNING: the old secret stops working immediately; update your endpoint right away or deliveries will fail verification. ' +
'`delete` — permanently removes the subscription. ' +
'`test` — sends a test event to confirm reachability and signature verification. ' +
'`rotate_secret` — returns a new signing secret — WARNING: the old secret stops working immediately; update your endpoint or deliveries fail verification. ' +
'`webhook_id` is required for every action except create.',

@@ -65,3 +62,3 @@ inputShape: {

.optional()
.describe('The webhook attributes (create: name, url, events; update: any of those plus is_active), forwarded verbatim.'),
.describe('The webhook attributes, forwarded verbatim.'),
},

@@ -68,0 +65,0 @@ annotations: { destructiveHint: true },

{
"name": "@labelgrid/mcp",
"version": "0.5.0",
"version": "0.6.0",
"mcpName": "io.github.labelgrid/labelgrid-mcp",

@@ -30,3 +30,3 @@ "description": "Official LabelGrid MCP server — connect your AI client to your LabelGrid account",

"dependencies": {
"@labelgrid/core": "0.2.0",
"@labelgrid/core": "0.2.1",
"@modelcontextprotocol/sdk": "^1.12.0",

@@ -33,0 +33,0 @@ "zod": "^3.24.0"

+14
-12

@@ -107,3 +107,3 @@ # LabelGrid MCP Server

_31 tools across 8 toolsets. This table is generated from the
_33 tools across 8 toolsets. This table is generated from the
tool definitions by `npm run gen-docs` — do not edit it by hand._

@@ -116,2 +116,3 @@

| `get_account` | read | Read the authenticated LabelGrid account. Pick ONE view with `view`: `profile` returns the account profile — including the release submission limit/quota and terms-acceptance status — use it to confirm which account your API token belongs to before making other calls; `balance` returns your accounting summary — current balance and related account-level financial totals. |
| `get_rate_limit` | read | The account’s API rate budget per category (read, write, export, analytics): ceiling, remaining, and window reset. Every token on the account shares one budget; a null ceiling means none applies. Free to call — use it to pace requests and to recover from a 429. |
| `revoke_api_token` | write | Revoke a LabelGrid API token. Pass token_id to revoke a specific token; omit it to revoke the token currently in use. WARNING: revoking the current token immediately ends this session — the server loses access and stops working until you configure a new token. |

@@ -123,3 +124,3 @@

| --- | --- | --- |
| `list_reference_data` | read | Fetch a LabelGrid reference dataset used to resolve the IDs and codes the catalog and release tools expect. Pick ONE dataset with `type`: `genres` and `genre_categories` (genre IDs), `languages` (audio/metadata language codes), `contributor_roles`, `instruments`, `distro_outlets` (the outlets/stores available to your account), `territories` (country codes), `issue_definitions` (each review issue code’s title, description, severity and whether it blocks distribution; codes are string slugs), or `webhook_event_types` (every webhook event type with its payload schema). Call this when you need a valid ID or code. The same datasets are exposed as MCP resources at labelgrid://reference/{type}; this tool is the fallback for clients that don’t surface resources. |
| `list_reference_data` | read | Fetch a LabelGrid reference dataset used to resolve the IDs and codes the catalog and release tools expect. Pick ONE dataset with `type`: `genres` and `genre_categories` (genre IDs), `languages` (audio/metadata language codes), `contributor_roles`, `instruments`, `distro_outlets` (the outlets/stores available to you), `territories` (country codes), `issue_definitions` (review issue codes — string slugs — with severity and whether they block distribution), or `webhook_event_types` (event types with their payload schemas). Also exposed as MCP resources at labelgrid://reference/{type}; this tool is the fallback. |

@@ -130,9 +131,9 @@ ### Catalog (labels, artists, writers, publishers, releases, tracks) `catalog`

| --- | --- | --- |
| `search_catalog` | read | List catalog entities of one kind, paginated. `filters` takes the endpoint’s own filter names, passed through verbatim — label: no documented filters — paginate with page/per_page. artist: artist_name (filter by artist name). writer: name (writer name), ipi (IPI number). publisher: name (publisher name), ipi (IPI number). release: label_id (owning label id), is_live (1 = live/distributed only), barcode_number (UPC/EAN), cat (catalog number). track: release_id (one release’s tracks), isrc (filter by ISRC). Use get_catalog_item for full detail. |
| `search_catalog` | read | List catalog entities of one kind, paginated. `filters` takes the endpoint’s own filter names, passed through verbatim — label: no documented filters. artist: artist_name. writer: name, ipi. publisher: name, ipi. release: label_id, is_live (1 = live only), barcode_number (UPC/EAN), cat. track: release_id, isrc. Use get_catalog_item for full detail. |
| `get_catalog_item` | read | Retrieve one catalog entity by id, with full detail (e.g. a release’s metadata and track listing, a track’s contributors and royalty splits, a writer’s PRO/IPI). |
| `create_catalog_item` | write | Create a catalog entity: pass its attributes in `fields` — the API owns all validation. Required and common fields per entity: label — required: name, default_email; optional: support email, website/platform URLs, default copyright lines, isrc_base. artist — required: artist_name; optional: full_name, email, location, bios, isni, default_language, platform profile URLs. writer — required: first_name, last_name; optional: middle_name, display_credits, email, country, pro, ipi, isni, publisher_id (or publisher_name/publisher_pro/publisher_ipi). publisher — required: name; optional: ipi, pro, isni, controlled_publisher. release — required on create: content_type, label_id, artists, titles, cat (catalog number), artwork_ai_usage, primary_genre_id; many optional fields (dates, copyright lines, genres, per-outlet URLs). Once submitted or distributed some fields are locked — changing one returns a 403 with code RELEASE_LOCKED_FIELDS naming exactly which fields cannot change. track — required on create: release_id, disc, track_num, composition_type, artists, audio_ai_usage, composition_ai_usage, commercial_samples, audio_language, contributors, and recording_country (ISO 3166-1 alpha-2, e.g. "US"); optional: titles, isrc, iswc, writers, publishers, splits, and more. A release is created in DRAFT state — add tracks, then run the release checks before distributing. |
| `create_catalog_item` | write | Create a catalog entity: pass its attributes in `fields` — the API owns all validation. Required and common fields per entity: label — required: name, default_email; optional: support email, website/platform URLs, default copyright lines, isrc_base. artist — required: artist_name; optional: full_name, email, location, bios, isni, default_language, platform profile URLs. writer — required: first_name, last_name; optional: middle_name, display_credits, email, country, pro, ipi, isni, publisher_id (or publisher_name/publisher_pro/publisher_ipi). publisher — required: name; optional: ipi, pro, isni, controlled_publisher. release — required on create: content_type, label_id, artists, titles, cat (catalog number), artwork_ai_usage, primary_genre_id; many optional fields (dates, copyright lines, genres, per-outlet URLs). track — required on create: release_id, disc, track_num, composition_type, artists, audio_ai_usage, composition_ai_usage, commercial_samples, audio_language, contributors, and recording_country (ISO 3166-1 alpha-2, e.g. "US"); optional: titles, isrc, iswc, writers, publishers, splits, and more. A release is created in DRAFT state — add tracks, then run the release checks before distributing. |
| `update_catalog_item` | write | Update a catalog entity: supply only the fields to change in `fields` (same field sets as create_catalog_item). Once a release is submitted or distributed, some release and track fields are locked — changing one returns a 403 with code RELEASE_LOCKED_FIELDS naming exactly which fields cannot change. |
| `delete_catalog_item` | write | Delete a catalog entity. The API refuses deletes that would orphan data — label: refused while the label still has releases — remove or reassign its releases first. artist: refused while still referenced by releases or tracks. writer: refused while still referenced by tracks. publisher: refused while still referenced by writers. release: only a never-submitted draft can be deleted. track: allowed while the parent release is an editable draft; refused once submitted or distributed. |
| `delete_catalog_item` | write | Delete a catalog entity. The API refuses deletes that would orphan data — label: refused while the label still has releases — remove or reassign its releases first. artist: refused while still referenced by releases or tracks. writer: refused while still referenced by tracks. publisher: refused while still referenced by writers. release: only a never-submitted draft can be deleted. track: refused once the release is no longer an editable draft. |
| `upload_image` | write | Upload a label image (logo, dark-mode logo, or background) or an artist photo from a local image file, per `target`. |
| `get_asset` | read | Read a track or release asset. Valid combinations: (1) mode='info' + parent='track' + asset stereo\|dolby\|lyrics — file metadata (not the bytes) incl. processing state. (2) mode='info' + parent='release' + asset square\|tall — motion-artwork (animated cover) video metadata. (3) mode='download_url' + parent='track' + asset audio_16\|audio_24\|audio_32 (WAV master) or audio_preview_full\|audio_preview_clip (MP3 preview) — returns { download_url, expires_in }, a signed URL that expires roughly 10 minutes after issue; fetch it directly — do not send your API token to it. Any other combination returns a structured error. |
| `get_asset` | read | Read a track or release asset. Valid combinations: (1) mode='info' + parent='track' + asset stereo\|dolby\|lyrics — file metadata (not the bytes) incl. processing state. (2) mode='info' + parent='release' + asset square\|tall — motion-artwork (animated cover) video metadata. (3) mode='download_url' + parent='track' + asset audio_16\|audio_24\|audio_32 (WAV master) or audio_preview_full\|audio_preview_clip (MP3 preview) — returns { download_url, expires_in }, a signed URL that expires roughly 10 minutes after issue; fetch it directly — do not send your API token to it. Any other combination is refused. |

@@ -155,4 +156,5 @@ ### Releases (review, delivery, links, licenses, checks) `releases`

| --- | --- | --- |
| `get_analytics` | read | Streaming analytics summary. Window capped at 400 days; `metrics` takes 1-12 section keys per request (split larger selections — responses are cached). KUGOU/KUWO/QQMUSIC report weekly: one point per week carrying the whole week — never average it per day. `meta` carries `platform_cadence`, `section_granularity`, `sections_as_of` and `sections_complete_through` (later dates still filling in). Call get_analytics_availability first for section-per-platform support. Rate-limited ~60/min; windows over 90 days draw a separate lower ~30/min budget — prefer shorter windows for polling. A 429 carries retry_after_seconds. |
| `get_analytics` | read | Streaming analytics summary. Window capped at 400 days; `metrics` takes 1-12 section keys per request (split larger selections — responses are cached). KUGOU/KUWO/QQMUSIC report weekly: one point per week carrying the whole week — never average it per day. `meta` carries `platform_cadence`, `section_granularity`, `sections_as_of` and `sections_complete_through` (later dates still filling in). Call get_analytics_availability first for section-per-platform support. The `social-*` / `soundcloud-engagement` sections cover social and UGC usage instead of streaming: their `platform` is a UGC platform; a use, view and play are distinct quantities, never summed with each other or with streams; `ugc_platform` narrows them. Selecting any adds `meta.social_availability` (which UGC platforms report each signal) — the streaming matrix excludes them. The `track-*-daily` sections need a `release_id`, `isrc` or `upc` scope. `track-listeners-daily` sums per-entry daily counts: not distinct people, not summable across dates. Rate-limited ~60/min; windows over 90 days draw a separate lower ~30/min budget — prefer shorter windows for polling. A 429 carries retry_after_seconds. |
| `get_analytics_availability` | read | Static `availability` matrix (per section, per platform) plus `platform_cadence` (daily\|weekly per platform). Account- and date-independent: fetch once, reuse. Read it before get_analytics so an unreported section is treated as unavailable, not an empty chart. |
| `get_analytics_rankings` | read | Top-N rankings for a window, ordered by summed streams. Pick ONE `view`: `leaderboards` — your top artists, tracks or albums (`type` required; `all` returns all three in one request). `placements` — the playlists and radio containers driving streams, summed across storefronts. Same scope filters as get_analytics; `limit` 1-50 (default 10). Under a `platform` filter, an `availability` of `not_available_for_platform` means that platform reports no ranking and `data` is empty. |
| `query_artificial_streaming` | read | Artificial-streaming (streaming-integrity) reads. Pick ONE `view`: `flags` — Stream Radar early-warning flags, paginated (`filters`: status, severity, dsp, isrc, release_id, detected_from/detected_to). Stream Radar is an optional add-on; without it the API returns a 403, surfaced verbatim. `flag_detail` — one flag by `flag_id`. `records` — reported artificial-streaming records, cursor-paginated; the detail behind any artificial-streaming fee (`filters`: dsp, start_date/end_date, release_id, isrc). `fee_breakdown` — per-release fee breakdown for one `period` (YYYY-MM). response_format:'detailed' returns the verbatim API response. |

@@ -164,4 +166,4 @@

| --- | --- | --- |
| `query_financials` | read | Query your financial data. Pick ONE view with `view`: `statements` lists your royalty statements, paginated — `filters`: label_id, release_id, isrc, upc, start_date/end_date; group_by="release" rolls totals up per release. `statement_detail` retrieves one statement by `invoice_number` (required). `transactions` lists account transactions, paginated — same `filters`; sort with `sort`; group_by="release" rolls up per release. `royalty_breakdown` returns a cursor-paginated royalty breakdown — `group_by` is REQUIRED for this view: a comma-separated, ordered subset of: track, dsp, release, territory, period (e.g. "release,dsp"); same `filters`; pass `cursor` to page. Use download_statement for statement line items (CSV) or the invoice PDF. response_format:'detailed' returns the verbatim API response. |
| `download_statement` | read | Download statement files. `format: 'csv'` downloads statement line items — pass invoice_number for one statement, OR a start_date/end_date range to export across statements; with save_to_path (an absolute path whose parent directory exists) the CSV is written there and the byte count returned; otherwise it is returned inline, truncated at 100KB (truncated: true) — use save_to_path for large exports. `format: 'invoice_pdf'` downloads the invoice PDF — invoice_number and save_to_path are both REQUIRED (the PDF is binary). An existing file is never overwritten (returns FILE_EXISTS). |
| `query_financials` | read | Query your financial data. Pick ONE view with `view`: `statements` lists your royalty statements, paginated — `filters`: label_id, release_id, isrc, upc, start_date/end_date; group_by="release" rolls totals up per release. `statement_detail` retrieves one statement by `invoice_number` (required). `transactions` lists account transactions, paginated — same `filters` and `group_by`; sort with `sort`. `royalty_breakdown` returns a cursor-paginated royalty breakdown — `group_by` is REQUIRED for this view: a comma-separated, ordered subset of: track, dsp, release, territory, period (e.g. "release,dsp"); same `filters`; pass `cursor` to page. Use download_statement for statement line items (CSV) or the invoice PDF. response_format:'detailed' returns the verbatim API response. |
| `download_statement` | read | Download statement files. `format: 'csv'` downloads statement line items — pass invoice_number for one statement, OR a start_date/end_date range to export across statements; with save_to_path the CSV is written there and the byte count returned; otherwise it is returned inline, truncated at 100KB (truncated: true) — use save_to_path for large exports. `format: 'invoice_pdf'` downloads the invoice PDF — invoice_number and save_to_path are both REQUIRED (the PDF is binary). An existing file is never overwritten (returns FILE_EXISTS). |

@@ -172,4 +174,4 @@ ### Webhooks (off by default — enable via LABELGRID_TOOLSETS) `webhooks`

| --- | --- | --- |
| `list_webhooks` | read | Read your webhook subscriptions. `view: 'config'` (the default) lists the webhook subscriptions configured on your account — each with its URL, subscribed events and active state — or retrieves one subscription when `webhook_id` is given. `view: 'logs'` retrieves the recent delivery log for a webhook (`webhook_id` required) — attempts, response codes and outcomes — to debug why events did or did not reach your endpoint. |
| `manage_webhook` | write | Manage a webhook subscription. Pick ONE action with `action`: `create` — pass `fields` with `name`, `url` (the HTTPS endpoint receiving deliveries) and `events` (the event subscription object — see list_reference_data type webhook_event_types); the API returns a signing secret ONCE on creation — store it to verify incoming payloads. `update` — supply only the fields to change in `fields`: name, url, events, or is_active (false pauses deliveries). `delete` — permanently removes the subscription; it stops receiving events. `test` — sends a test event to confirm reachability and signature verification; safe to repeat. `rotate_secret` — generates and returns a new signing secret — WARNING: the old secret stops working immediately; update your endpoint right away or deliveries will fail verification. `webhook_id` is required for every action except create. |
| `list_webhooks` | read | Read your webhook subscriptions. `view: 'config'` (the default) lists them — URL, subscribed events, active state — or retrieves one when `webhook_id` is given. `view: 'logs'` retrieves the recent delivery log for a webhook (`webhook_id` required) — attempts, response codes and outcomes — to debug why events did or did not reach your endpoint. |
| `manage_webhook` | write | Manage a webhook subscription. Pick ONE action with `action`: `create` — `fields`: `name`, `url` (the HTTPS endpoint receiving deliveries), `events` (see list_reference_data type webhook_event_types); the signing secret is returned ONCE on creation — store it to verify payloads. `update` — supply only the fields to change in `fields`: name, url, events, or is_active (false pauses deliveries). `delete` — permanently removes the subscription. `test` — sends a test event to confirm reachability and signature verification. `rotate_secret` — returns a new signing secret — WARNING: the old secret stops working immediately; update your endpoint or deliveries fail verification. `webhook_id` is required for every action except create. |

@@ -180,3 +182,3 @@ ### Distribution (full writes) `distribution`

| --- | --- | --- |
| `upload_asset` | full-write | Upload a finalized track or release asset from a local file. `id` is the track id for track_* targets, the release id for release_*. `track_stereo` (WAV/FLAC/AIFF), `track_dolby` (Dolby Atmos WAV) and `track_lyrics` (LRC) process asynchronously — check state with get_asset (mode info). `release_cover_art` uploads or replaces the static cover art image. `release_motion_square` / `release_motion_tall` upload the square or tall animated cover (motion artwork) video, also asynchronous. All assets become immutable once the release is distributed — upload final files first. |
| `upload_asset` | full-write | Upload a finalized track or release asset from a local file. `track_stereo` (WAV/FLAC/AIFF), `track_dolby` (Dolby Atmos WAV) and `track_lyrics` (LRC) process asynchronously — check state with get_asset (mode info). `release_cover_art` uploads or replaces the static cover art image. `release_motion_square`/`release_motion_tall` upload the animated cover (motion artwork) video, also asynchronous. All assets become immutable once the release is distributed — upload final files first. |
| `delete_asset` | full-write | Delete a track asset (track_*) or an animated cover / motion artwork video (release_motion_*). Allowed only while the parent release is an editable draft; refused once locked or distributed. Cover art cannot be deleted. |

@@ -183,0 +185,0 @@ | `manage_track_license` | full-write | Manage license documents on a track (cover or cleared sample). `upload` attaches a new license — `file_path` required, `type` selects cover/sample; optional metadata fields. `update` replaces the file and/or metadata — `track_license_id` (from list_track_licenses) and `file_path` required. `delete` permanently removes a license and its file — `track_license_id` required; cannot be undone. Immutability-governed once the release is live. |

@@ -5,3 +5,3 @@ {

"description": "Official LabelGrid MCP server — manage your music catalog, releases, analytics and distribution.",
"version": "0.4.0",
"version": "0.6.0",
"websiteUrl": "https://labelgrid.com",

@@ -16,3 +16,3 @@ "repository": {

"identifier": "@labelgrid/mcp",
"version": "0.4.0",
"version": "0.6.0",
"transport": {

@@ -19,0 +19,0 @@ "type": "stdio"