@labelgrid/mcp 0.2.2 → 0.3.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 +29 -0
- package/README.md +132 -100
- package/dist/config.d.ts +15 -0
- package/dist/config.js +37 -5
- package/dist/coverage.js +79 -79
- package/dist/entities.d.ts +25 -0
- package/dist/entities.js +51 -0
- package/dist/gating.d.ts +1 -1
- package/dist/gating.js +5 -1
- package/dist/projection.d.ts +40 -0
- package/dist/projection.js +145 -0
- package/dist/resources.d.ts +29 -0
- package/dist/resources.js +107 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +23 -3
- package/dist/tools/account.d.ts +3 -0
- package/dist/tools/account.js +32 -0
- package/dist/tools/all.js +12 -20
- package/dist/tools/catalog.d.ts +14 -0
- package/dist/tools/catalog.js +267 -0
- package/dist/tools/distribution.d.ts +13 -0
- package/dist/tools/distribution.js +266 -0
- package/dist/tools/finance.d.ts +12 -0
- package/dist/tools/finance.js +311 -0
- package/dist/tools/insights.d.ts +7 -0
- package/dist/tools/insights.js +139 -0
- package/dist/tools/reference.js +9 -24
- package/dist/tools/releases.d.ts +11 -0
- package/dist/tools/releases.js +177 -0
- package/dist/tools/setup.js +1 -1
- package/dist/tools/webhooks.d.ts +3 -3
- package/dist/tools/webhooks.js +73 -105
- package/package.json +3 -2
- package/server.json +3 -3
- package/dist/tools/accounting.d.ts +0 -12
- package/dist/tools/accounting.js +0 -386
- package/dist/tools/analytics.d.ts +0 -3
- package/dist/tools/analytics.js +0 -62
- package/dist/tools/catalog-read.d.ts +0 -10
- package/dist/tools/catalog-read.js +0 -145
- package/dist/tools/catalog-write.d.ts +0 -12
- package/dist/tools/catalog-write.js +0 -206
- package/dist/tools/delivery.d.ts +0 -6
- package/dist/tools/delivery.js +0 -40
- package/dist/tools/files-read.d.ts +0 -7
- package/dist/tools/files-read.js +0 -86
- package/dist/tools/full-writes.d.ts +0 -12
- package/dist/tools/full-writes.js +0 -248
- package/dist/tools/identity.d.ts +0 -3
- package/dist/tools/identity.js +0 -28
- package/dist/tools/release-write.d.ts +0 -12
- package/dist/tools/release-write.js +0 -184
- package/dist/tools/review-read.d.ts +0 -7
- package/dist/tools/review-read.js +0 -78
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Catalog write toolset (safe writes): create/update/delete for labels,
|
|
3
|
-
* artists, writers and publishers, plus multipart image uploads.
|
|
4
|
-
*
|
|
5
|
-
* Create/update forward a permissive `fields` object straight to the API, which
|
|
6
|
-
* owns all validation — this keeps each tool a thin wrapper and lets the full
|
|
7
|
-
* documented field set through without re-declaring it here. The tool
|
|
8
|
-
* descriptions name the required and common fields; consult the LabelGrid API
|
|
9
|
-
* docs for the complete list.
|
|
10
|
-
*/
|
|
11
|
-
import { statSync } from 'node:fs';
|
|
12
|
-
import { z } from 'zod';
|
|
13
|
-
import { assertAllowedExtension } from '../api/content-types.js';
|
|
14
|
-
/** Accepted image extensions for the catalog image uploads. */
|
|
15
|
-
const IMAGE_EXTS = ['.jpg', '.jpeg', '.png', '.webp', '.tif', '.tiff'];
|
|
16
|
-
/** A permissive body of API fields, forwarded verbatim to the endpoint. */
|
|
17
|
-
function fieldsBody(desc) {
|
|
18
|
-
return z.record(z.string(), z.unknown()).describe(desc);
|
|
19
|
-
}
|
|
20
|
-
/** Rejects a path that is not an existing regular file, before any HTTP call. */
|
|
21
|
-
function fileError(p) {
|
|
22
|
-
let isFile = false;
|
|
23
|
-
try {
|
|
24
|
-
isFile = statSync(p).isFile();
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
isFile = false;
|
|
28
|
-
}
|
|
29
|
-
return isFile
|
|
30
|
-
? null
|
|
31
|
-
: { code: 'FILE_NOT_FOUND', message: `No readable file at ${p}.`, status: 0 };
|
|
32
|
-
}
|
|
33
|
-
function createTool(spec) {
|
|
34
|
-
return {
|
|
35
|
-
name: spec.name,
|
|
36
|
-
toolset: 'catalog',
|
|
37
|
-
gate: 'safe_write',
|
|
38
|
-
title: spec.title,
|
|
39
|
-
description: spec.description,
|
|
40
|
-
inputShape: { fields: fieldsBody(spec.fieldsDesc) },
|
|
41
|
-
annotations: {},
|
|
42
|
-
handler: (args, { client }) => client.post(spec.path, args.fields),
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
function updateTool(spec) {
|
|
46
|
-
return {
|
|
47
|
-
name: spec.name,
|
|
48
|
-
toolset: 'catalog',
|
|
49
|
-
gate: 'safe_write',
|
|
50
|
-
title: spec.title,
|
|
51
|
-
description: spec.description,
|
|
52
|
-
inputShape: {
|
|
53
|
-
[spec.idField]: z.number().int().positive(),
|
|
54
|
-
fields: fieldsBody(spec.fieldsDesc),
|
|
55
|
-
},
|
|
56
|
-
annotations: {},
|
|
57
|
-
handler: (args, { client }) => client.patch(`${spec.basePath}/${args[spec.idField]}`, args.fields),
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
function deleteTool(spec) {
|
|
61
|
-
return {
|
|
62
|
-
name: spec.name,
|
|
63
|
-
toolset: 'catalog',
|
|
64
|
-
gate: 'safe_write',
|
|
65
|
-
title: spec.title,
|
|
66
|
-
description: spec.description,
|
|
67
|
-
inputShape: { [spec.idField]: z.number().int().positive() },
|
|
68
|
-
annotations: { destructiveHint: true },
|
|
69
|
-
handler: (args, { client }) => client.delete(`${spec.basePath}/${args[spec.idField]}`),
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
const uploadLabelImage = {
|
|
73
|
-
name: 'upload_label_image',
|
|
74
|
-
toolset: 'catalog',
|
|
75
|
-
gate: 'safe_write',
|
|
76
|
-
title: 'Upload a label image',
|
|
77
|
-
description: 'Upload a label image from a local file. `image_type` selects which asset: logo, logo-dark (a dark-mode variant), or background. `file_path` must be a local image file.',
|
|
78
|
-
inputShape: {
|
|
79
|
-
label_id: z.number().int().positive(),
|
|
80
|
-
image_type: z.enum(['logo', 'logo-dark', 'background']),
|
|
81
|
-
file_path: z.string().describe('Local path to the image file to upload.'),
|
|
82
|
-
},
|
|
83
|
-
annotations: {},
|
|
84
|
-
handler: async (args, { client }) => {
|
|
85
|
-
const err = fileError(args.file_path);
|
|
86
|
-
if (err)
|
|
87
|
-
return { error: err };
|
|
88
|
-
const ext = assertAllowedExtension(args.file_path, IMAGE_EXTS);
|
|
89
|
-
if ('error' in ext)
|
|
90
|
-
return { error: ext.error };
|
|
91
|
-
return client.postMultipart(`/labels/${args.label_id}/images/${args.image_type}`, ext.realPath, 'file');
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
const uploadArtistPhoto = {
|
|
95
|
-
name: 'upload_artist_photo',
|
|
96
|
-
toolset: 'catalog',
|
|
97
|
-
gate: 'safe_write',
|
|
98
|
-
title: 'Upload an artist photo',
|
|
99
|
-
description: 'Upload an artist photo from a local file. `file_path` must be a local image file.',
|
|
100
|
-
inputShape: {
|
|
101
|
-
artist_id: z.number().int().positive(),
|
|
102
|
-
file_path: z.string().describe('Local path to the photo file to upload.'),
|
|
103
|
-
},
|
|
104
|
-
annotations: {},
|
|
105
|
-
handler: async (args, { client }) => {
|
|
106
|
-
const err = fileError(args.file_path);
|
|
107
|
-
if (err)
|
|
108
|
-
return { error: err };
|
|
109
|
-
const ext = assertAllowedExtension(args.file_path, IMAGE_EXTS);
|
|
110
|
-
if ('error' in ext)
|
|
111
|
-
return { error: ext.error };
|
|
112
|
-
return client.postMultipart(`/artists/${args.artist_id}/photo`, ext.realPath, 'file');
|
|
113
|
-
},
|
|
114
|
-
};
|
|
115
|
-
export const catalogWriteTools = [
|
|
116
|
-
createTool({
|
|
117
|
-
name: 'create_label',
|
|
118
|
-
path: '/labels',
|
|
119
|
-
title: 'Create a label',
|
|
120
|
-
description: 'Create a new label. Pass its attributes in `fields`.',
|
|
121
|
-
fieldsDesc: 'Label attributes. Required: name (string), default_email (string). Common optional: active, support_email, website_url, spotify_url, applemusic_url, default_copyright_name_p_line, default_copyright_name_c_line, isrc_base, enable_website, image. See the API docs for the full field list.',
|
|
122
|
-
}),
|
|
123
|
-
updateTool({
|
|
124
|
-
name: 'update_label',
|
|
125
|
-
idField: 'label_id',
|
|
126
|
-
basePath: '/labels',
|
|
127
|
-
title: 'Update a label',
|
|
128
|
-
description: 'Update a label. Supply only the fields you want to change in `fields`.',
|
|
129
|
-
fieldsDesc: 'Label fields to change (same field set as create_label). Supply only what changes.',
|
|
130
|
-
}),
|
|
131
|
-
deleteTool({
|
|
132
|
-
name: 'delete_label',
|
|
133
|
-
idField: 'label_id',
|
|
134
|
-
basePath: '/labels',
|
|
135
|
-
title: 'Delete a label',
|
|
136
|
-
description: 'Delete a label. The API refuses to delete a label that still has releases — remove or reassign its releases first.',
|
|
137
|
-
}),
|
|
138
|
-
uploadLabelImage,
|
|
139
|
-
createTool({
|
|
140
|
-
name: 'create_artist',
|
|
141
|
-
path: '/artists',
|
|
142
|
-
title: 'Create an artist',
|
|
143
|
-
description: 'Create a new artist. Pass its attributes in `fields`.',
|
|
144
|
-
fieldsDesc: 'Artist attributes. Required: artist_name (string). Common optional: full_name, email, location, bio_short, bio_full, isni, default_language, and platform profile URLs (spotify_url, applemusic_url, youtube_url, etc.). See the API docs for the full field list.',
|
|
145
|
-
}),
|
|
146
|
-
updateTool({
|
|
147
|
-
name: 'update_artist',
|
|
148
|
-
idField: 'artist_id',
|
|
149
|
-
basePath: '/artists',
|
|
150
|
-
title: 'Update an artist',
|
|
151
|
-
description: 'Update an artist. Supply only the fields you want to change in `fields`.',
|
|
152
|
-
fieldsDesc: 'Artist fields to change (same field set as create_artist). Supply only what changes.',
|
|
153
|
-
}),
|
|
154
|
-
deleteTool({
|
|
155
|
-
name: 'delete_artist',
|
|
156
|
-
idField: 'artist_id',
|
|
157
|
-
basePath: '/artists',
|
|
158
|
-
title: 'Delete an artist',
|
|
159
|
-
description: 'Delete an artist. The API refuses deletion when the artist is still referenced by releases or tracks.',
|
|
160
|
-
}),
|
|
161
|
-
uploadArtistPhoto,
|
|
162
|
-
createTool({
|
|
163
|
-
name: 'create_writer',
|
|
164
|
-
path: '/writers',
|
|
165
|
-
title: 'Create a writer',
|
|
166
|
-
description: 'Create a new songwriter. Pass its attributes in `fields`.',
|
|
167
|
-
fieldsDesc: 'Writer attributes. Required: first_name (string), last_name (string). Common optional: middle_name, display_credits, email, country, pro, ipi, isni, publisher_id (or publisher_name/publisher_pro/publisher_ipi). See the API docs for the full field list.',
|
|
168
|
-
}),
|
|
169
|
-
updateTool({
|
|
170
|
-
name: 'update_writer',
|
|
171
|
-
idField: 'writer_id',
|
|
172
|
-
basePath: '/writers',
|
|
173
|
-
title: 'Update a writer',
|
|
174
|
-
description: 'Update a writer. Supply only the fields you want to change in `fields`.',
|
|
175
|
-
fieldsDesc: 'Writer fields to change (same field set as create_writer). Supply only what changes.',
|
|
176
|
-
}),
|
|
177
|
-
deleteTool({
|
|
178
|
-
name: 'delete_writer',
|
|
179
|
-
idField: 'writer_id',
|
|
180
|
-
basePath: '/writers',
|
|
181
|
-
title: 'Delete a writer',
|
|
182
|
-
description: 'Delete a writer. The API refuses deletion when the writer is still referenced by tracks.',
|
|
183
|
-
}),
|
|
184
|
-
createTool({
|
|
185
|
-
name: 'create_publisher',
|
|
186
|
-
path: '/publishers',
|
|
187
|
-
title: 'Create a publisher',
|
|
188
|
-
description: 'Create a new publisher. Pass its attributes in `fields`.',
|
|
189
|
-
fieldsDesc: 'Publisher attributes. Required: name (string). Common optional: ipi, pro, isni, controlled_publisher. See the API docs for the full field list.',
|
|
190
|
-
}),
|
|
191
|
-
updateTool({
|
|
192
|
-
name: 'update_publisher',
|
|
193
|
-
idField: 'publisher_id',
|
|
194
|
-
basePath: '/publishers',
|
|
195
|
-
title: 'Update a publisher',
|
|
196
|
-
description: 'Update a publisher. Supply only the fields you want to change in `fields`.',
|
|
197
|
-
fieldsDesc: 'Publisher fields to change (same field set as create_publisher). Supply only what changes.',
|
|
198
|
-
}),
|
|
199
|
-
deleteTool({
|
|
200
|
-
name: 'delete_publisher',
|
|
201
|
-
idField: 'publisher_id',
|
|
202
|
-
basePath: '/publishers',
|
|
203
|
-
title: 'Delete a publisher',
|
|
204
|
-
description: 'Delete a publisher. The API refuses deletion when the publisher is still referenced by writers.',
|
|
205
|
-
}),
|
|
206
|
-
];
|
package/dist/tools/delivery.d.ts
DELETED
package/dist/tools/delivery.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Delivery reads: the distribution queue and a release's smart-link landing
|
|
3
|
-
* page configuration. Both read-only, in the `delivery` toolset.
|
|
4
|
-
*/
|
|
5
|
-
import { z } from 'zod';
|
|
6
|
-
const getDeliveryQueue = {
|
|
7
|
-
name: 'get_delivery_queue',
|
|
8
|
-
toolset: 'delivery',
|
|
9
|
-
gate: 'read',
|
|
10
|
-
title: 'Get the distribution queue',
|
|
11
|
-
description: 'List the distribution queue entries for your account, paginated — one entry per (release, outlet) delivery with its current status (e.g. pending review, processing, scheduled, complete, error). Filter by `release_id`, `outlet_id`, or `status`. Use this to see where a release is in the delivery pipeline to each store.',
|
|
12
|
-
inputShape: {
|
|
13
|
-
release_id: z.number().int().positive().optional().describe('Filter to one release.'),
|
|
14
|
-
outlet_id: z.number().int().positive().optional().describe('Filter to one outlet/store.'),
|
|
15
|
-
status: z.string().optional().describe('Filter by delivery status.'),
|
|
16
|
-
page: z.number().int().positive().optional(),
|
|
17
|
-
per_page: z.number().int().positive().optional(),
|
|
18
|
-
},
|
|
19
|
-
annotations: { readOnlyHint: true },
|
|
20
|
-
handler: (args, { client }) => client.get('/queues/distro', {
|
|
21
|
-
page: args.page,
|
|
22
|
-
per_page: args.per_page,
|
|
23
|
-
filter: {
|
|
24
|
-
release_id: args.release_id,
|
|
25
|
-
outlet_id: args.outlet_id,
|
|
26
|
-
status: args.status,
|
|
27
|
-
},
|
|
28
|
-
}),
|
|
29
|
-
};
|
|
30
|
-
const getLandingConfig = {
|
|
31
|
-
name: 'get_landing_config',
|
|
32
|
-
toolset: 'delivery',
|
|
33
|
-
gate: 'read',
|
|
34
|
-
title: 'Get a release landing-page config',
|
|
35
|
-
description: 'Retrieve the smart-link landing-page configuration for a release: whether the links page is enabled, its style/mode, custom copy, the action list and any pre-order links. Pair with update_landing_config to change it.',
|
|
36
|
-
inputShape: { release_id: z.number().int().positive() },
|
|
37
|
-
annotations: { readOnlyHint: true },
|
|
38
|
-
handler: (args, { client }) => client.get(`/releases/${args.release_id}/landing-config`),
|
|
39
|
-
};
|
|
40
|
-
export const deliveryTools = [getDeliveryQueue, getLandingConfig];
|
package/dist/tools/files-read.js
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Catalog file reads: track and release asset lookups, a signed audio
|
|
3
|
-
* download URL, and track license reads. All read-only, in the `catalog`
|
|
4
|
-
* toolset.
|
|
5
|
-
*/
|
|
6
|
-
import { z } from 'zod';
|
|
7
|
-
/** Track asset kinds the API exposes. */
|
|
8
|
-
const TRACK_FILE_TYPE = z
|
|
9
|
-
.enum(['stereo', 'dolby', 'lyrics'])
|
|
10
|
-
.describe('Which track asset: stereo audio, Dolby Atmos audio, or the lyrics file.');
|
|
11
|
-
/** Downloadable track audio asset kinds for the signed download-url endpoint. */
|
|
12
|
-
const TRACK_DOWNLOAD_ASSET_TYPE = z
|
|
13
|
-
.enum(['audio_16', 'audio_24', 'audio_32', 'audio_preview_full', 'audio_preview_clip'])
|
|
14
|
-
.describe('Which downloadable asset: audio_16/audio_24/audio_32 = the WAV master at that bit depth; ' +
|
|
15
|
-
'audio_preview_full/audio_preview_clip = the generated MP3 preview (full-length / clip).');
|
|
16
|
-
/** Release animated cover (motion artwork) video kinds. */
|
|
17
|
-
const RELEASE_ASSET_TYPE = z
|
|
18
|
-
.enum(['square', 'tall'])
|
|
19
|
-
.describe('Which animated cover (motion artwork) video: the square or the tall/portrait cover video.');
|
|
20
|
-
const getTrackFile = {
|
|
21
|
-
name: 'get_track_file',
|
|
22
|
-
toolset: 'catalog',
|
|
23
|
-
gate: 'read',
|
|
24
|
-
title: 'Get a track file',
|
|
25
|
-
description: 'Retrieve metadata about one of a track’s asset files (its stereo audio, Dolby Atmos audio, or lyrics file), including its processing state. This returns file information, not the bytes — use get_track_audio_download_url for a downloadable link.',
|
|
26
|
-
inputShape: { track_id: z.number().int().positive(), file_type: TRACK_FILE_TYPE },
|
|
27
|
-
annotations: { readOnlyHint: true },
|
|
28
|
-
handler: (args, { client }) => client.get(`/tracks/${args.track_id}/files/${args.file_type}`),
|
|
29
|
-
};
|
|
30
|
-
const getTrackAudioDownloadUrl = {
|
|
31
|
-
name: 'get_track_audio_download_url',
|
|
32
|
-
toolset: 'catalog',
|
|
33
|
-
gate: 'read',
|
|
34
|
-
title: 'Get a track audio download URL',
|
|
35
|
-
description: 'Return a time-limited, signed URL to download one of a track’s audio assets. `asset_type` selects the asset: audio_16, audio_24, and audio_32 are the WAV master at that bit depth; audio_preview_full and audio_preview_clip are the generated MP3 preview (full-length / clip). Returns { download_url, expires_in }; the URL expires roughly 10 minutes after it is issued, so request a fresh one when it lapses. Fetch the URL directly — do not send your API token to it.',
|
|
36
|
-
inputShape: { track_id: z.number().int().positive(), asset_type: TRACK_DOWNLOAD_ASSET_TYPE },
|
|
37
|
-
annotations: { readOnlyHint: true },
|
|
38
|
-
handler: (args, { client }) => client.get(`/tracks/${args.track_id}/files/${args.asset_type}/download-url`),
|
|
39
|
-
};
|
|
40
|
-
const listTrackLicenses = {
|
|
41
|
-
name: 'list_track_licenses',
|
|
42
|
-
toolset: 'catalog',
|
|
43
|
-
gate: 'read',
|
|
44
|
-
title: 'List track licenses',
|
|
45
|
-
description: 'List the licenses attached to a track (e.g. cover/mechanical or sample clearances), paginated.',
|
|
46
|
-
inputShape: {
|
|
47
|
-
track_id: z.number().int().positive(),
|
|
48
|
-
page: z.number().int().positive().optional(),
|
|
49
|
-
per_page: z.number().int().positive().optional(),
|
|
50
|
-
},
|
|
51
|
-
annotations: { readOnlyHint: true },
|
|
52
|
-
handler: (args, { client }) => client.get(`/tracks/${args.track_id}/licenses`, {
|
|
53
|
-
page: args.page,
|
|
54
|
-
per_page: args.per_page,
|
|
55
|
-
}),
|
|
56
|
-
};
|
|
57
|
-
const getTrackLicense = {
|
|
58
|
-
name: 'get_track_license',
|
|
59
|
-
toolset: 'catalog',
|
|
60
|
-
gate: 'read',
|
|
61
|
-
title: 'Get a track license',
|
|
62
|
-
description: 'Retrieve one license attached to a track by its license id.',
|
|
63
|
-
inputShape: {
|
|
64
|
-
track_id: z.number().int().positive(),
|
|
65
|
-
license_id: z.number().int().positive(),
|
|
66
|
-
},
|
|
67
|
-
annotations: { readOnlyHint: true },
|
|
68
|
-
handler: (args, { client }) => client.get(`/tracks/${args.track_id}/licenses/${args.license_id}`),
|
|
69
|
-
};
|
|
70
|
-
const getReleaseFile = {
|
|
71
|
-
name: 'get_release_file',
|
|
72
|
-
toolset: 'catalog',
|
|
73
|
-
gate: 'read',
|
|
74
|
-
title: 'Get a release file',
|
|
75
|
-
description: 'Retrieve metadata about a release animated cover (motion artwork) video asset — the square or the tall/portrait cover video — including its processing state.',
|
|
76
|
-
inputShape: { release_id: z.number().int().positive(), asset_type: RELEASE_ASSET_TYPE },
|
|
77
|
-
annotations: { readOnlyHint: true },
|
|
78
|
-
handler: (args, { client }) => client.get(`/releases/${args.release_id}/files/${args.asset_type}`),
|
|
79
|
-
};
|
|
80
|
-
export const filesReadTools = [
|
|
81
|
-
getTrackFile,
|
|
82
|
-
getTrackAudioDownloadUrl,
|
|
83
|
-
listTrackLicenses,
|
|
84
|
-
getTrackLicense,
|
|
85
|
-
getReleaseFile,
|
|
86
|
-
];
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Full-write toolset (`distribution`): the consequential actions that put a
|
|
3
|
-
* release into the world or change immutable assets. Every tool here is gated
|
|
4
|
-
* `full_write`, so it is neither registered nor callable unless the operator
|
|
5
|
-
* has explicitly armed full writes (the flag AND the acknowledgment sentence).
|
|
6
|
-
*
|
|
7
|
-
* These wrap: finalized audio/artwork uploads (via the presigned-URL flow),
|
|
8
|
-
* license file management, the FINAL distribute/takedown actions, the
|
|
9
|
-
* Preflight-QC confirm-review step, and one-time Beatport onboarding.
|
|
10
|
-
*/
|
|
11
|
-
import type { ToolDef } from './types.js';
|
|
12
|
-
export declare const fullWriteTools: ToolDef[];
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Full-write toolset (`distribution`): the consequential actions that put a
|
|
3
|
-
* release into the world or change immutable assets. Every tool here is gated
|
|
4
|
-
* `full_write`, so it is neither registered nor callable unless the operator
|
|
5
|
-
* has explicitly armed full writes (the flag AND the acknowledgment sentence).
|
|
6
|
-
*
|
|
7
|
-
* These wrap: finalized audio/artwork uploads (via the presigned-URL flow),
|
|
8
|
-
* license file management, the FINAL distribute/takedown actions, the
|
|
9
|
-
* Preflight-QC confirm-review step, and one-time Beatport onboarding.
|
|
10
|
-
*/
|
|
11
|
-
import { z } from 'zod';
|
|
12
|
-
import { assertAllowedExtension } from '../api/content-types.js';
|
|
13
|
-
import { uploadViaPresignedUrl } from '../api/upload.js';
|
|
14
|
-
/** Per-tool upload extension allow-lists: an upload tool never reads an arbitrary file. */
|
|
15
|
-
const AUDIO_EXTS = {
|
|
16
|
-
stereo: ['.wav', '.flac', '.aif', '.aiff'],
|
|
17
|
-
dolby: ['.wav'],
|
|
18
|
-
lyrics: ['.lrc', '.txt'],
|
|
19
|
-
};
|
|
20
|
-
const ANIMATED_COVER_EXTS = ['.mp4', '.mov'];
|
|
21
|
-
const IMAGE_EXTS = ['.jpg', '.jpeg', '.png', '.webp', '.tif', '.tiff'];
|
|
22
|
-
const LICENSE_EXTS = ['.pdf', '.jpg', '.jpeg', '.png'];
|
|
23
|
-
const TRACK_FILE_TYPE = z
|
|
24
|
-
.enum(['stereo', 'dolby', 'lyrics'])
|
|
25
|
-
.describe('Which track asset: stereo audio, Dolby Atmos audio, or the lyrics (LRC) file.');
|
|
26
|
-
const RELEASE_ASSET_TYPE = z
|
|
27
|
-
.enum(['square', 'tall'])
|
|
28
|
-
.describe('Which animated cover (motion artwork) video: the square or the tall/portrait cover video.');
|
|
29
|
-
const trackId = z.number().int().positive().describe('The track id.');
|
|
30
|
-
const releaseId = z.number().int().positive().describe('The release id.');
|
|
31
|
-
const labelId = z.number().int().positive().describe('The label id.');
|
|
32
|
-
const filePath = z.string().describe('Local path to the file to upload.');
|
|
33
|
-
/** Optional caller-supplied idempotency key, plumbed to the Idempotency-Key header. */
|
|
34
|
-
const idempotencyKey = z
|
|
35
|
-
.string()
|
|
36
|
-
.min(8)
|
|
37
|
-
.max(128)
|
|
38
|
-
.optional()
|
|
39
|
-
.describe('Optional idempotency key. The server deduplicates by this key for 24h — pass the SAME key when retrying a call whose outcome you did not observe. Without it, each call is a new operation.');
|
|
40
|
-
/** Optional license metadata shared by the license upload/update tools. */
|
|
41
|
-
const licenseMeta = {
|
|
42
|
-
license_id: z.string().optional().describe('The license/clearance reference number, if any.'),
|
|
43
|
-
license_provider: z
|
|
44
|
-
.enum(['licensing_agency', 'direct_from_publisher'])
|
|
45
|
-
.optional()
|
|
46
|
-
.describe('Where the license came from.'),
|
|
47
|
-
license_provider_name: z.string().optional().describe('The name of the license provider.'),
|
|
48
|
-
original_track_link: z.string().optional().describe('URL to the original/source track.'),
|
|
49
|
-
};
|
|
50
|
-
/** Collects the defined license metadata fields into a string map for multipart. */
|
|
51
|
-
function licenseExtra(args) {
|
|
52
|
-
const out = {};
|
|
53
|
-
for (const key of [
|
|
54
|
-
'type',
|
|
55
|
-
'license_id',
|
|
56
|
-
'license_provider',
|
|
57
|
-
'license_provider_name',
|
|
58
|
-
'original_track_link',
|
|
59
|
-
]) {
|
|
60
|
-
const v = args[key];
|
|
61
|
-
if (v !== undefined && v !== null)
|
|
62
|
-
out[key] = String(v);
|
|
63
|
-
}
|
|
64
|
-
return out;
|
|
65
|
-
}
|
|
66
|
-
const uploadTrackAudio = {
|
|
67
|
-
name: 'upload_track_audio',
|
|
68
|
-
toolset: 'distribution',
|
|
69
|
-
gate: 'full_write',
|
|
70
|
-
title: 'Upload a track audio/lyrics file',
|
|
71
|
-
description: 'Upload a finalized audio file (stereo WAV/FLAC or Dolby Atmos) or lyrics (LRC) file for a track. The file is uploaded directly to storage and then processed asynchronously; check its state with get_track_file. Once a release is distributed the file is immutable — upload the correct master before distributing.',
|
|
72
|
-
inputShape: { track_id: trackId, file_type: TRACK_FILE_TYPE, file_path: filePath },
|
|
73
|
-
annotations: {},
|
|
74
|
-
handler: async (args, { client }) => {
|
|
75
|
-
const ext = assertAllowedExtension(args.file_path, AUDIO_EXTS[args.file_type]);
|
|
76
|
-
if ('error' in ext)
|
|
77
|
-
return { error: ext.error };
|
|
78
|
-
return uploadViaPresignedUrl(client, {
|
|
79
|
-
uploadUrlPath: `/tracks/${args.track_id}/files/${args.file_type}/upload-url`,
|
|
80
|
-
commitPath: `/tracks/${args.track_id}/files/${args.file_type}`,
|
|
81
|
-
filePath: ext.realPath,
|
|
82
|
-
});
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
const deleteTrackAudio = {
|
|
86
|
-
name: 'delete_track_audio',
|
|
87
|
-
toolset: 'distribution',
|
|
88
|
-
gate: 'full_write',
|
|
89
|
-
title: 'Delete a track audio/lyrics file',
|
|
90
|
-
description: 'Delete one of a track’s asset files (stereo, Dolby Atmos, or lyrics). Allowed only while the parent release is still an editable draft; the API refuses once the release is locked or distributed.',
|
|
91
|
-
inputShape: { track_id: trackId, file_type: TRACK_FILE_TYPE },
|
|
92
|
-
annotations: { destructiveHint: true },
|
|
93
|
-
handler: (args, { client }) => client.delete(`/tracks/${args.track_id}/files/${args.file_type}`),
|
|
94
|
-
};
|
|
95
|
-
const uploadReleaseAsset = {
|
|
96
|
-
name: 'upload_release_asset',
|
|
97
|
-
toolset: 'distribution',
|
|
98
|
-
gate: 'full_write',
|
|
99
|
-
title: 'Upload a release animated cover video',
|
|
100
|
-
description: 'Upload a finalized animated cover (motion artwork) video for a release — the square or the tall/portrait cover video. The video is uploaded directly to storage and then processed; check its state with get_release_file. Upload the correct video before distributing — it is immutable once the release is live. For the static cover art image use upload_release_artwork.',
|
|
101
|
-
inputShape: { release_id: releaseId, asset_type: RELEASE_ASSET_TYPE, file_path: filePath },
|
|
102
|
-
annotations: {},
|
|
103
|
-
handler: async (args, { client }) => {
|
|
104
|
-
const ext = assertAllowedExtension(args.file_path, ANIMATED_COVER_EXTS);
|
|
105
|
-
if ('error' in ext)
|
|
106
|
-
return { error: ext.error };
|
|
107
|
-
return uploadViaPresignedUrl(client, {
|
|
108
|
-
uploadUrlPath: `/releases/${args.release_id}/files/${args.asset_type}/upload-url`,
|
|
109
|
-
commitPath: `/releases/${args.release_id}/files/${args.asset_type}`,
|
|
110
|
-
filePath: ext.realPath,
|
|
111
|
-
});
|
|
112
|
-
},
|
|
113
|
-
};
|
|
114
|
-
const deleteReleaseAsset = {
|
|
115
|
-
name: 'delete_release_asset',
|
|
116
|
-
toolset: 'distribution',
|
|
117
|
-
gate: 'full_write',
|
|
118
|
-
title: 'Delete a release animated cover video',
|
|
119
|
-
description: 'Delete a release animated cover (motion artwork) video — the square or the tall/portrait cover video. Allowed only while the release is still an editable draft.',
|
|
120
|
-
inputShape: { release_id: releaseId, asset_type: RELEASE_ASSET_TYPE },
|
|
121
|
-
annotations: { destructiveHint: true },
|
|
122
|
-
handler: (args, { client }) => client.delete(`/releases/${args.release_id}/files/${args.asset_type}`),
|
|
123
|
-
};
|
|
124
|
-
const uploadReleaseArtwork = {
|
|
125
|
-
name: 'upload_release_artwork',
|
|
126
|
-
toolset: 'distribution',
|
|
127
|
-
gate: 'full_write',
|
|
128
|
-
title: 'Upload release cover art',
|
|
129
|
-
description: 'Upload or replace the release’s static cover art image from a local file. Cover art is immutable once the release is distributed — upload the final artwork before distributing.',
|
|
130
|
-
inputShape: { release_id: releaseId, file_path: filePath },
|
|
131
|
-
annotations: {},
|
|
132
|
-
handler: async (args, { client }) => {
|
|
133
|
-
const ext = assertAllowedExtension(args.file_path, IMAGE_EXTS);
|
|
134
|
-
if ('error' in ext)
|
|
135
|
-
return { error: ext.error };
|
|
136
|
-
return client.postMultipart(`/releases/${args.release_id}/photo`, ext.realPath, 'file');
|
|
137
|
-
},
|
|
138
|
-
};
|
|
139
|
-
const uploadTrackLicense = {
|
|
140
|
-
name: 'upload_track_license',
|
|
141
|
-
toolset: 'distribution',
|
|
142
|
-
gate: 'full_write',
|
|
143
|
-
title: 'Upload a track license',
|
|
144
|
-
description: 'Attach a license document to a track (for a cover or a cleared sample). `file_path` is the local license file and `type` is "cover" or "sample". Optionally record license_id, license_provider, license_provider_name, and original_track_link. Licenses are immutability-governed once the release is live.',
|
|
145
|
-
inputShape: {
|
|
146
|
-
track_id: trackId,
|
|
147
|
-
file_path: filePath,
|
|
148
|
-
type: z.enum(['cover', 'sample']).describe('The kind of license: a cover or a sample license.'),
|
|
149
|
-
...licenseMeta,
|
|
150
|
-
},
|
|
151
|
-
annotations: {},
|
|
152
|
-
handler: async (args, { client }) => {
|
|
153
|
-
const ext = assertAllowedExtension(args.file_path, LICENSE_EXTS);
|
|
154
|
-
if ('error' in ext)
|
|
155
|
-
return { error: ext.error };
|
|
156
|
-
return client.postMultipart(`/tracks/${args.track_id}/licenses`, ext.realPath, 'file', licenseExtra(args));
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
const updateTrackLicense = {
|
|
160
|
-
name: 'update_track_license',
|
|
161
|
-
toolset: 'distribution',
|
|
162
|
-
gate: 'full_write',
|
|
163
|
-
title: 'Update a track license',
|
|
164
|
-
description: 'Replace the file and/or metadata of an existing track license. `track_license_id` is the id of the license (from list_track_licenses); `file_path` is the license file to submit. Optionally update license_id, license_provider, license_provider_name, and original_track_link.',
|
|
165
|
-
inputShape: {
|
|
166
|
-
track_id: trackId,
|
|
167
|
-
track_license_id: z.number().int().positive().describe('The track license id to update.'),
|
|
168
|
-
file_path: filePath,
|
|
169
|
-
...licenseMeta,
|
|
170
|
-
},
|
|
171
|
-
annotations: {},
|
|
172
|
-
handler: async (args, { client }) => {
|
|
173
|
-
const ext = assertAllowedExtension(args.file_path, LICENSE_EXTS);
|
|
174
|
-
if ('error' in ext)
|
|
175
|
-
return { error: ext.error };
|
|
176
|
-
return client.postMultipart(`/tracks/${args.track_id}/licenses/${args.track_license_id}`, ext.realPath, 'file', licenseExtra(args));
|
|
177
|
-
},
|
|
178
|
-
};
|
|
179
|
-
const deleteTrackLicense = {
|
|
180
|
-
name: 'delete_track_license',
|
|
181
|
-
toolset: 'distribution',
|
|
182
|
-
gate: 'full_write',
|
|
183
|
-
title: 'Delete a track license',
|
|
184
|
-
description: 'Permanently delete a track license and its file. `track_license_id` is the license id (from list_track_licenses). This cannot be undone.',
|
|
185
|
-
inputShape: {
|
|
186
|
-
track_id: trackId,
|
|
187
|
-
track_license_id: z.number().int().positive().describe('The track license id to delete.'),
|
|
188
|
-
},
|
|
189
|
-
annotations: { destructiveHint: true },
|
|
190
|
-
handler: (args, { client }) => client.delete(`/tracks/${args.track_id}/licenses/${args.track_license_id}`),
|
|
191
|
-
};
|
|
192
|
-
const distributeRelease = {
|
|
193
|
-
name: 'distribute_release',
|
|
194
|
-
toolset: 'distribution',
|
|
195
|
-
gate: 'full_write',
|
|
196
|
-
title: 'Distribute a release',
|
|
197
|
-
description: 'Submit a release for distribution to the stores/outlets — this is the FINAL, consequential action that sends the release out; validate_release should pass first. The server enforces your account’s weekly submission limit and returns a structured error if it is exceeded. Pass idempotency_key and reuse the SAME value if you retry a call whose outcome you did not observe — the server deduplicates by it for 24h; without a key each call is a new submission.',
|
|
198
|
-
inputShape: { release_id: releaseId, idempotency_key: idempotencyKey },
|
|
199
|
-
annotations: { destructiveHint: true },
|
|
200
|
-
handler: (args, { client }) => client.post(`/releases/${args.release_id}/distribute`, undefined, {
|
|
201
|
-
idempotency: true,
|
|
202
|
-
idempotencyKey: args.idempotency_key,
|
|
203
|
-
}),
|
|
204
|
-
};
|
|
205
|
-
const takedownRelease = {
|
|
206
|
-
name: 'takedown_release',
|
|
207
|
-
toolset: 'distribution',
|
|
208
|
-
gate: 'full_write',
|
|
209
|
-
title: 'Take down a release',
|
|
210
|
-
description: 'Take a release down from ALL outlets/stores — a final, consequential action that removes it everywhere it was delivered. Re-distribution afterward is a fresh submission.',
|
|
211
|
-
inputShape: { release_id: releaseId },
|
|
212
|
-
annotations: { destructiveHint: true },
|
|
213
|
-
handler: (args, { client }) => client.post(`/releases/${args.release_id}/takedown-all`),
|
|
214
|
-
};
|
|
215
|
-
const confirmReview = {
|
|
216
|
-
name: 'confirm_review',
|
|
217
|
-
toolset: 'distribution',
|
|
218
|
-
gate: 'full_write',
|
|
219
|
-
title: 'Confirm a held release into review',
|
|
220
|
-
description: 'Confirm a release that Preflight QC placed on hold, moving it into distribution review. Use after you have reviewed the quality report and accept the release as-is. Safe to repeat.',
|
|
221
|
-
inputShape: { release_id: releaseId },
|
|
222
|
-
annotations: { idempotentHint: true },
|
|
223
|
-
handler: (args, { client }) => client.post(`/releases/${args.release_id}/confirm-review`),
|
|
224
|
-
};
|
|
225
|
-
const enableBeatport = {
|
|
226
|
-
name: 'enable_beatport',
|
|
227
|
-
toolset: 'distribution',
|
|
228
|
-
gate: 'full_write',
|
|
229
|
-
title: 'Request Beatport onboarding for a label',
|
|
230
|
-
description: 'Request Beatport onboarding for a label. This is a one-time action that cannot be un-requested once submitted, so confirm the label is correct first.',
|
|
231
|
-
inputShape: { label_id: labelId },
|
|
232
|
-
annotations: { destructiveHint: true },
|
|
233
|
-
handler: (args, { client }) => client.post(`/labels/${args.label_id}/enable-beatport`),
|
|
234
|
-
};
|
|
235
|
-
export const fullWriteTools = [
|
|
236
|
-
uploadTrackAudio,
|
|
237
|
-
deleteTrackAudio,
|
|
238
|
-
uploadReleaseAsset,
|
|
239
|
-
deleteReleaseAsset,
|
|
240
|
-
uploadReleaseArtwork,
|
|
241
|
-
uploadTrackLicense,
|
|
242
|
-
updateTrackLicense,
|
|
243
|
-
deleteTrackLicense,
|
|
244
|
-
distributeRelease,
|
|
245
|
-
takedownRelease,
|
|
246
|
-
confirmReview,
|
|
247
|
-
enableBeatport,
|
|
248
|
-
];
|
package/dist/tools/identity.d.ts
DELETED
package/dist/tools/identity.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/** Identity toolset: read the authenticated account, revoke API tokens. */
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
const getMe = {
|
|
4
|
-
name: 'get_me',
|
|
5
|
-
toolset: 'identity',
|
|
6
|
-
gate: 'read',
|
|
7
|
-
title: 'Get my account',
|
|
8
|
-
description: 'Return the authenticated LabelGrid account profile, including the release submission limit/quota and terms-acceptance status. Use this to confirm which account your API token belongs to before making other calls.',
|
|
9
|
-
inputShape: {},
|
|
10
|
-
annotations: { readOnlyHint: true },
|
|
11
|
-
handler: (_args, { client }) => client.get('/me'),
|
|
12
|
-
};
|
|
13
|
-
const revokeApiToken = {
|
|
14
|
-
name: 'revoke_api_token',
|
|
15
|
-
toolset: 'identity',
|
|
16
|
-
gate: 'safe_write',
|
|
17
|
-
title: 'Revoke an API token',
|
|
18
|
-
description: '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.',
|
|
19
|
-
inputShape: { token_id: z.number().int().positive().optional() },
|
|
20
|
-
annotations: { destructiveHint: true, idempotentHint: true },
|
|
21
|
-
handler: (args, { client }) => {
|
|
22
|
-
const tokenId = args.token_id;
|
|
23
|
-
return tokenId === undefined
|
|
24
|
-
? client.delete('/tokens/current')
|
|
25
|
-
: client.delete(`/tokens/${tokenId}`);
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
export const identityTools = [getMe, revokeApiToken];
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Release write toolset (safe writes): the release/track draft lifecycle plus
|
|
3
|
-
* validate, quality-report refresh, landing-page config, a short URL, and
|
|
4
|
-
* review-issue notes.
|
|
5
|
-
*
|
|
6
|
-
* create_release and create_track send an auto-generated Idempotency-Key so a
|
|
7
|
-
* retried creation cannot duplicate the entity. Release/track create+update
|
|
8
|
-
* forward a permissive `fields` object straight to the API, which owns all
|
|
9
|
-
* validation; the descriptions name the required fields.
|
|
10
|
-
*/
|
|
11
|
-
import type { ToolDef } from './types.js';
|
|
12
|
-
export declare const releaseWriteTools: ToolDef[];
|