@labelgrid/mcp 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +3 -3
- package/dist/coverage.js +1 -0
- package/dist/projection.js +1 -0
- package/dist/tools/catalog.js +35 -5
- package/dist/tools/insights.js +20 -25
- package/package.json +2 -2
- package/server.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,37 @@ All notable changes to `@labelgrid/mcp` are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.7.0] - 2026-08-20
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `delete_catalog_item` accepts `replace_with` for writers and publishers. The
|
|
13
|
+
replacement receives every credit before the original is deleted. Other
|
|
14
|
+
entity types reject the parameter without sending a delete request.
|
|
15
|
+
- `query_financials` concise responses include `credit_amount`, preserving
|
|
16
|
+
positive, zero and explicit null values from the API.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Writer and publisher deletion guidance now names all references that can
|
|
21
|
+
block deletion and documents the `REPLACEMENT_UNAVAILABLE` response.
|
|
22
|
+
|
|
23
|
+
## [0.6.1] - 2026-08-05
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- `get_analytics` states how the summary aggregates: every section aggregates
|
|
28
|
+
over the resolved filter scope, so a `upc` filter returns release totals
|
|
29
|
+
rather than a row per track. Per-track output comes from the `track-*-daily`
|
|
30
|
+
sections (which need a `release_id`, `isrc` or `upc` scope) or from
|
|
31
|
+
`get_analytics_rankings`.
|
|
32
|
+
- `get_analytics_rankings` is described as the per-entity breakdown of a scope
|
|
33
|
+
as well as a leaderboard — `type: 'tracks'` with a `upc` or `release_id`
|
|
34
|
+
filter ranks the tracks on that release.
|
|
35
|
+
- Descriptions on the `metrics`, `view` and `type` parameters were removed where
|
|
36
|
+
they restated the parameter's own enum or its tool description. No parameter,
|
|
37
|
+
enum value or validation rule changed.
|
|
38
|
+
|
|
8
39
|
## [0.6.0] - 2026-08-05
|
|
9
40
|
|
|
10
41
|
### Added
|
package/README.md
CHANGED
|
@@ -129,7 +129,7 @@ tool definitions by `npm run gen-docs` — do not edit it by hand._
|
|
|
129
129
|
| `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). |
|
|
130
130
|
| `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. |
|
|
131
131
|
| `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. |
|
|
132
|
-
| `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
|
|
132
|
+
| `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 or artists, unless replace_with reassigns those credits. publisher: refused while still referenced by tracks or label default publishers, unless replace_with reassigns those credits. release: only a never-submitted draft can be deleted. track: refused once the release is no longer an editable draft. `replace_with` — writer and publisher only — is the id inheriting those credits; a 422 REPLACEMENT_UNAVAILABLE means nothing was deleted. |
|
|
133
133
|
| `upload_image` | write | Upload a label image (logo, dark-mode logo, or background) or an artist photo from a local image file, per `target`. |
|
|
134
134
|
| `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. |
|
|
135
135
|
|
|
@@ -149,9 +149,9 @@ tool definitions by `npm run gen-docs` — do not edit it by hand._
|
|
|
149
149
|
|
|
150
150
|
| Tool | Gate | Description |
|
|
151
151
|
| --- | --- | --- |
|
|
152
|
-
| `get_analytics` | read | Streaming analytics summary. Window capped at 400 days; `metrics` takes 1-12 section keys per request (split larger selections
|
|
152
|
+
| `get_analytics` | read | Streaming analytics summary. Window capped at 400 days; `metrics` takes 1-12 section keys per request (split larger selections; responses cached). KUGOU/KUWO/QQMUSIC report weekly: one point carries the whole week — never average per day. `meta` carries `platform_cadence`, `section_granularity`, `sections_as_of` and `sections_complete_through` (later dates still filling in). See get_analytics_availability for per-platform support. Sections aggregate over the resolved filter scope: a `upc` filter gives release totals, not per-track rows. For those use `track-*-daily` (needs a `release_id`, `isrc` or `upc` scope) or get_analytics_rankings. `track-listeners-daily` sums per-entry daily counts: not distinct people, not summable across dates. `social-*` / `soundcloud-engagement` cover social/UGC usage, not streaming: `platform` is a UGC platform, `ugc_platform` narrows them; use, view and play are distinct quantities never summed together or with streams. Selecting any adds `meta.social_availability` (which UGC platforms report each signal); the streaming matrix omits them. Rate-limited ~60/min; windows over 90 days draw a separate ~30/min budget — prefer short windows for polling. A 429 carries retry_after_seconds. |
|
|
153
153
|
| `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. |
|
|
154
|
-
| `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
|
|
154
|
+
| `get_analytics_rankings` | read | Top-N rankings for a window, ordered by summed streams — the per-entity breakdown of a scope: `type` tracks with a `upc`/`release_id` filter ranks the tracks on that release. Pick ONE `view`: `leaderboards` — your top artists, tracks or albums (`type` required; `all` returns all three in one call). `placements` — playlists and radio containers driving streams, summed across storefronts. Same scope filters as get_analytics; `limit` 1-50 (default 10). Under a `platform` filter, `availability: not_available_for_platform` means no ranking there and `data` is empty. |
|
|
155
155
|
| `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. |
|
|
156
156
|
|
|
157
157
|
### Finance (statements, transactions, royalties) `finance`
|
package/dist/coverage.js
CHANGED
|
@@ -145,6 +145,7 @@ export const EXCLUDED = {
|
|
|
145
145
|
'GET /resolve/label/{labelSlug}': 'label-website resolution — not exposed in v1',
|
|
146
146
|
'GET /site-settings/{label}': 'label-website settings — not exposed in v1',
|
|
147
147
|
'GET /site-settings/links/{label}': 'label-website settings — not exposed in v1',
|
|
148
|
+
'GET /tracks/{track}/file-upload-attempts/{uploadAttempt}': 'per-upload audio processing status — not exposed in v1',
|
|
148
149
|
'GET /tracks/{track}/licenses/{trackLicense}/download': 'license file download — not exposed in v1',
|
|
149
150
|
'GET /transactions/csv': 'transaction CSV export — not exposed in v1',
|
|
150
151
|
};
|
package/dist/projection.js
CHANGED
package/dist/tools/catalog.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* `response_format: 'detailed'` returning the verbatim API response.
|
|
12
12
|
*/
|
|
13
13
|
import { statSync } from 'node:fs';
|
|
14
|
-
import { ENTITIES, ENTITY_NAMES, assertAllowedExtension, } from '@labelgrid/core';
|
|
14
|
+
import { ENTITIES, ENTITY_NAMES, REPLACEMENT_ENTITIES, assertAllowedExtension, } from '@labelgrid/core';
|
|
15
15
|
import { z } from 'zod';
|
|
16
16
|
import { applyProjection } from '../projection.js';
|
|
17
17
|
/** Accepted image extensions for the catalog image uploads. */
|
|
@@ -135,17 +135,47 @@ const updateCatalogItem = {
|
|
|
135
135
|
return client.patch(`${spec.path}/${args.id}`, args.fields);
|
|
136
136
|
},
|
|
137
137
|
};
|
|
138
|
+
/** 'writer and publisher' — read off the registry, never spelled out by hand. */
|
|
139
|
+
const REPLACEMENT_ENTITY_LIST = REPLACEMENT_ENTITIES.join(' and ');
|
|
138
140
|
const deleteCatalogItem = {
|
|
139
141
|
name: 'delete_catalog_item',
|
|
140
142
|
toolset: 'catalog',
|
|
141
143
|
gate: 'safe_write',
|
|
142
144
|
title: 'Delete a catalog item',
|
|
143
|
-
description: `Delete a catalog entity. The API refuses deletes that would orphan data — ${entityDoc((s) => s.deleteNote)}
|
|
144
|
-
|
|
145
|
+
description: `Delete a catalog entity. The API refuses deletes that would orphan data — ${entityDoc((s) => s.deleteNote)}` +
|
|
146
|
+
` \`replace_with\` — ${REPLACEMENT_ENTITY_LIST} only — is the id inheriting those credits; a 422 REPLACEMENT_UNAVAILABLE means nothing was deleted.`,
|
|
147
|
+
inputShape: {
|
|
148
|
+
entity: entityArg,
|
|
149
|
+
id: idArg,
|
|
150
|
+
replace_with: z
|
|
151
|
+
.number()
|
|
152
|
+
.int()
|
|
153
|
+
.positive()
|
|
154
|
+
.optional()
|
|
155
|
+
.describe(`${REPLACEMENT_ENTITY_LIST} only: the id that takes over every credit held by the one being deleted.`),
|
|
156
|
+
},
|
|
145
157
|
annotations: { destructiveHint: true },
|
|
146
158
|
handler: (args, { client }) => {
|
|
147
|
-
const
|
|
148
|
-
|
|
159
|
+
const entity = args.entity;
|
|
160
|
+
const spec = ENTITIES[entity];
|
|
161
|
+
const path = `${spec.path}/${args.id}`;
|
|
162
|
+
const replaceWith = args.replace_with;
|
|
163
|
+
if (replaceWith === undefined) {
|
|
164
|
+
return client.delete(path);
|
|
165
|
+
}
|
|
166
|
+
// Only two endpoints take the parameter. Dropping it for the others would
|
|
167
|
+
// delete on the caller's behalf while silently ignoring the reassignment
|
|
168
|
+
// they asked for, so the call is refused before anything is sent.
|
|
169
|
+
if (!spec.acceptsDeleteReplacement) {
|
|
170
|
+
return Promise.resolve({
|
|
171
|
+
error: {
|
|
172
|
+
code: 'INVALID_SELECTOR',
|
|
173
|
+
message: `The ${entity} delete does not accept replace_with — only ${REPLACEMENT_ENTITY_LIST} deletes do. Delete without it, or clear the references first.`,
|
|
174
|
+
status: 0,
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return client.delete(path, { replace_with: replaceWith });
|
|
149
179
|
},
|
|
150
180
|
};
|
|
151
181
|
/** Maps the label image targets to the API's imageType path segment. */
|
package/dist/tools/insights.js
CHANGED
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { applyProjection } from '../projection.js';
|
|
9
9
|
/**
|
|
10
|
-
* The 47 metric sections the summary endpoint can return
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* The 47 metric sections the summary endpoint can return. The server's canonical
|
|
11
|
+
* order — the order it projects sections into the response — is the streaming
|
|
12
|
+
* sections, then the per-track daily series, then the social and UGC family.
|
|
13
|
+
* This list groups the social and UGC family last instead; only membership
|
|
14
|
+
* matters here, since the enum validates which keys are legal, not their order.
|
|
13
15
|
*/
|
|
14
16
|
const METRICS = [
|
|
15
17
|
'streams',
|
|
@@ -96,25 +98,18 @@ const getAnalytics = {
|
|
|
96
98
|
toolset: 'insights',
|
|
97
99
|
gate: 'read',
|
|
98
100
|
title: 'Get streaming and social analytics',
|
|
99
|
-
description: 'Streaming analytics summary. Window capped at 400 days; `metrics` takes 1-12 section keys per request (split larger selections
|
|
100
|
-
'KUGOU/KUWO/QQMUSIC report weekly: one point
|
|
101
|
-
'
|
|
102
|
-
'
|
|
103
|
-
'
|
|
104
|
-
'Rate-limited ~60/min; windows over 90 days draw a separate
|
|
101
|
+
description: 'Streaming analytics summary. Window capped at 400 days; `metrics` takes 1-12 section keys per request (split larger selections; responses cached). ' +
|
|
102
|
+
'KUGOU/KUWO/QQMUSIC report weekly: one point carries the whole week — never average per day. `meta` carries `platform_cadence`, `section_granularity`, `sections_as_of` and `sections_complete_through` (later dates still filling in). ' +
|
|
103
|
+
'See get_analytics_availability for per-platform support. ' +
|
|
104
|
+
'Sections aggregate over the resolved filter scope: a `upc` filter gives release totals, not per-track rows. For those use `track-*-daily` (needs a `release_id`, `isrc` or `upc` scope) or get_analytics_rankings. `track-listeners-daily` sums per-entry daily counts: not distinct people, not summable across dates. ' +
|
|
105
|
+
'`social-*` / `soundcloud-engagement` cover social/UGC usage, not streaming: `platform` is a UGC platform, `ugc_platform` narrows them; use, view and play are distinct quantities never summed together or with streams. Selecting any adds `meta.social_availability` (which UGC platforms report each signal); the streaming matrix omits them. ' +
|
|
106
|
+
'Rate-limited ~60/min; windows over 90 days draw a separate ~30/min budget — prefer short windows for polling. A 429 carries retry_after_seconds.',
|
|
105
107
|
inputShape: {
|
|
106
108
|
start_date: z.string().describe('Window start, YYYY-MM-DD.'),
|
|
107
109
|
end_date: z.string().describe('Window end, YYYY-MM-DD.'),
|
|
108
|
-
metrics: z
|
|
109
|
-
.array(z.enum(METRICS))
|
|
110
|
-
.min(1)
|
|
111
|
-
.max(MAX_METRICS_PER_REQUEST)
|
|
112
|
-
.describe('Section keys, 1-12 per request.'),
|
|
110
|
+
metrics: z.array(z.enum(METRICS)).min(1).max(MAX_METRICS_PER_REQUEST),
|
|
113
111
|
platform: z.enum(PLATFORMS).optional(),
|
|
114
|
-
ugc_platform: z
|
|
115
|
-
.enum(UGC_PLATFORMS)
|
|
116
|
-
.optional()
|
|
117
|
-
.describe('Narrows the social/UGC sections only.'),
|
|
112
|
+
ugc_platform: z.enum(UGC_PLATFORMS).optional().describe('Social/UGC sections only.'),
|
|
118
113
|
release_id: z.number().int().positive().optional(),
|
|
119
114
|
isrc: z.string().optional(),
|
|
120
115
|
upc: z.string().optional(),
|
|
@@ -158,15 +153,15 @@ const getAnalyticsRankings = {
|
|
|
158
153
|
toolset: 'insights',
|
|
159
154
|
gate: 'read',
|
|
160
155
|
title: 'Get analytics rankings',
|
|
161
|
-
description: 'Top-N rankings for a window, ordered by summed streams. Pick ONE `view`: ' +
|
|
162
|
-
'`leaderboards` — your top artists, tracks or albums (`type` required; `all` returns all three in one
|
|
163
|
-
'`placements` —
|
|
164
|
-
'Same scope filters as get_analytics; `limit` 1-50 (default 10). Under a `platform` filter,
|
|
156
|
+
description: 'Top-N rankings for a window, ordered by summed streams — the per-entity breakdown of a scope: `type` tracks with a `upc`/`release_id` filter ranks the tracks on that release. Pick ONE `view`: ' +
|
|
157
|
+
'`leaderboards` — your top artists, tracks or albums (`type` required; `all` returns all three in one call). ' +
|
|
158
|
+
'`placements` — playlists and radio containers driving streams, summed across storefronts. ' +
|
|
159
|
+
'Same scope filters as get_analytics; `limit` 1-50 (default 10). Under a `platform` filter, `availability: not_available_for_platform` means no ranking there and `data` is empty.',
|
|
165
160
|
inputShape: {
|
|
166
|
-
view: z.enum(RANKING_VIEWS)
|
|
161
|
+
view: z.enum(RANKING_VIEWS),
|
|
167
162
|
start_date: z.string().describe('Window start, YYYY-MM-DD.'),
|
|
168
163
|
end_date: z.string().describe('Window end, YYYY-MM-DD.'),
|
|
169
|
-
type: z.enum(LEADERBOARD_TYPES).optional()
|
|
164
|
+
type: z.enum(LEADERBOARD_TYPES).optional(),
|
|
170
165
|
platform: z.enum(PLATFORMS).optional(),
|
|
171
166
|
ugc_platform: z.enum(UGC_PLATFORMS).optional(),
|
|
172
167
|
release_id: z.number().int().positive().optional(),
|
|
@@ -178,7 +173,7 @@ const getAnalyticsRankings = {
|
|
|
178
173
|
.int()
|
|
179
174
|
.positive()
|
|
180
175
|
.optional()
|
|
181
|
-
.describe('Narrow to one of your own labels;
|
|
176
|
+
.describe('Narrow to one of your own labels; never widens scope.'),
|
|
182
177
|
limit: z.number().int().positive().max(MAX_RANKING_LIMIT).optional(),
|
|
183
178
|
},
|
|
184
179
|
annotations: { readOnlyHint: true },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@labelgrid/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"mcpName": "io.github.labelgrid/labelgrid-mcp",
|
|
5
5
|
"description": "Official LabelGrid MCP server — connect your AI client to your LabelGrid account",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"node": ">=20"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@labelgrid/core": "0.2.
|
|
30
|
+
"@labelgrid/core": "0.2.2",
|
|
31
31
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
32
32
|
"zod": "^3.24.0"
|
|
33
33
|
}
|
package/server.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.labelgrid/labelgrid-mcp",
|
|
4
4
|
"description": "Official LabelGrid MCP server — manage your music catalog, releases, analytics and distribution.",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.7.0",
|
|
6
6
|
"websiteUrl": "https://labelgrid.com",
|
|
7
7
|
"repository": {
|
|
8
8
|
"url": "https://github.com/labelgrid/labelgrid-mcp",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "@labelgrid/mcp",
|
|
15
|
-
"version": "0.
|
|
15
|
+
"version": "0.7.0",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|