@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
package/dist/server.js
CHANGED
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
* protocol errors).
|
|
10
10
|
*/
|
|
11
11
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
12
|
+
import { defaultExcludedToolsets } from './config.js';
|
|
12
13
|
import { isToolEnabled } from './gating.js';
|
|
13
14
|
import { DATA_HANDLING_NOTE, FULL_WRITES_NOTICE, LEGAL_SUMMARY } from './legal.js';
|
|
14
15
|
import { log } from './log.js';
|
|
16
|
+
import { registerReferenceResources } from './resources.js';
|
|
15
17
|
import { setupTools } from './tools/setup.js';
|
|
16
18
|
import { toToolResult } from './tools/types.js';
|
|
17
19
|
import { VERSION } from './version.js';
|
|
@@ -51,10 +53,14 @@ export function buildServer(config, client, tools) {
|
|
|
51
53
|
for (const tool of registered) {
|
|
52
54
|
const isSetupHelper = tool.toolset === 'setup';
|
|
53
55
|
// Listing rule: connected mode applies the full gate matrix; setup mode
|
|
54
|
-
// lists the whole catalog (
|
|
55
|
-
//
|
|
56
|
+
// lists the whole catalog (honoring an explicit toolset narrowing, and
|
|
57
|
+
// otherwise the same default exclusion the connected surface applies — so
|
|
58
|
+
// the advertised surface matches reality), because nothing can execute
|
|
59
|
+
// without a token anyway.
|
|
56
60
|
const listable = config.setupMode
|
|
57
|
-
? config.toolsets === null
|
|
61
|
+
? config.toolsets === null
|
|
62
|
+
? !defaultExcludedToolsets.has(tool.toolset)
|
|
63
|
+
: config.toolsets.has(tool.toolset)
|
|
58
64
|
: isToolEnabled(tool, config);
|
|
59
65
|
if (!isSetupHelper && !listable)
|
|
60
66
|
continue;
|
|
@@ -101,5 +107,19 @@ export function buildServer(config, client, tools) {
|
|
|
101
107
|
return toToolResult(result);
|
|
102
108
|
});
|
|
103
109
|
}
|
|
110
|
+
// The nine labelgrid://reference/{type} resources follow the SAME listing
|
|
111
|
+
// rule as the reference tools: they are registered only when the `reference`
|
|
112
|
+
// toolset is enabled. Reference is not default-excluded, so an unset
|
|
113
|
+
// LABELGRID_TOOLSETS (or one naming `reference`) registers them; an explicit
|
|
114
|
+
// selection omitting `reference` disables them. Setup mode keeps them
|
|
115
|
+
// registered-but-inert under the same rule (reads return NOT_CONNECTED).
|
|
116
|
+
const referenceEnabled = config.setupMode
|
|
117
|
+
? config.toolsets === null
|
|
118
|
+
? !defaultExcludedToolsets.has('reference')
|
|
119
|
+
: config.toolsets.has('reference')
|
|
120
|
+
: isToolEnabled({ gate: 'read', toolset: 'reference' }, config);
|
|
121
|
+
if (referenceEnabled) {
|
|
122
|
+
registerReferenceResources(server, config, client);
|
|
123
|
+
}
|
|
104
124
|
return server;
|
|
105
125
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Account toolset: read the authenticated account, revoke API tokens. */
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
const getAccount = {
|
|
4
|
+
name: 'get_account',
|
|
5
|
+
toolset: 'account',
|
|
6
|
+
gate: 'read',
|
|
7
|
+
title: 'Get account',
|
|
8
|
+
description: 'Read the authenticated LabelGrid account. Pick ONE view with `view`: ' +
|
|
9
|
+
'`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; ' +
|
|
10
|
+
'`balance` returns your accounting summary — current balance and related account-level financial totals.',
|
|
11
|
+
inputShape: {
|
|
12
|
+
view: z.enum(['profile', 'balance']).describe('Which account read.'),
|
|
13
|
+
},
|
|
14
|
+
annotations: { readOnlyHint: true },
|
|
15
|
+
handler: (args, { client }) => client.get(args.view === 'balance' ? '/account' : '/me'),
|
|
16
|
+
};
|
|
17
|
+
const revokeApiToken = {
|
|
18
|
+
name: 'revoke_api_token',
|
|
19
|
+
toolset: 'account',
|
|
20
|
+
gate: 'safe_write',
|
|
21
|
+
title: 'Revoke an API token',
|
|
22
|
+
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.',
|
|
23
|
+
inputShape: { token_id: z.number().int().positive().optional() },
|
|
24
|
+
annotations: { destructiveHint: true, idempotentHint: true },
|
|
25
|
+
handler: (args, { client }) => {
|
|
26
|
+
const tokenId = args.token_id;
|
|
27
|
+
return tokenId === undefined
|
|
28
|
+
? client.delete('/tokens/current')
|
|
29
|
+
: client.delete(`/tokens/${tokenId}`);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
export const accountTools = [getAccount, revokeApiToken];
|
package/dist/tools/all.js
CHANGED
|
@@ -1,29 +1,21 @@
|
|
|
1
1
|
/** The complete tool catalog, in registration order. */
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { filesReadTools } from './files-read.js';
|
|
8
|
-
import { fullWriteTools } from './full-writes.js';
|
|
9
|
-
import { identityTools } from './identity.js';
|
|
2
|
+
import { accountTools } from './account.js';
|
|
3
|
+
import { catalogTools } from './catalog.js';
|
|
4
|
+
import { distributionTools } from './distribution.js';
|
|
5
|
+
import { financeTools } from './finance.js';
|
|
6
|
+
import { insightsTools } from './insights.js';
|
|
10
7
|
import { referenceTools } from './reference.js';
|
|
11
|
-
import {
|
|
12
|
-
import { reviewReadTools } from './review-read.js';
|
|
8
|
+
import { releaseTools } from './releases.js';
|
|
13
9
|
import { webhookTools } from './webhooks.js';
|
|
14
10
|
export function allTools() {
|
|
15
11
|
return [
|
|
16
|
-
...
|
|
12
|
+
...accountTools,
|
|
17
13
|
...referenceTools,
|
|
18
|
-
...
|
|
19
|
-
...
|
|
20
|
-
...
|
|
21
|
-
...
|
|
22
|
-
...deliveryTools,
|
|
23
|
-
...accountingTools,
|
|
14
|
+
...catalogTools,
|
|
15
|
+
...releaseTools,
|
|
16
|
+
...insightsTools,
|
|
17
|
+
...financeTools,
|
|
24
18
|
...webhookTools,
|
|
25
|
-
...
|
|
26
|
-
...releaseWriteTools,
|
|
27
|
-
...fullWriteTools,
|
|
19
|
+
...distributionTools,
|
|
28
20
|
];
|
|
29
21
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog toolset: the consolidated entity CRUD (search/get/create/update/
|
|
3
|
+
* delete across labels, artists, writers, publishers, releases and tracks),
|
|
4
|
+
* image uploads, and the asset read (`get_asset`).
|
|
5
|
+
*
|
|
6
|
+
* Every tool selects its entity via the registry in `src/entities.ts`.
|
|
7
|
+
* Create/update forward a permissive `fields` object straight to the API,
|
|
8
|
+
* which owns all validation — this keeps each tool a thin wrapper and lets the
|
|
9
|
+
* full documented field set through without re-declaring it here. The two
|
|
10
|
+
* reads are [proj] tools: concise-mode projection by default, with
|
|
11
|
+
* `response_format: 'detailed'` returning the verbatim API response.
|
|
12
|
+
*/
|
|
13
|
+
import type { ToolDef } from './types.js';
|
|
14
|
+
export declare const catalogTools: ToolDef[];
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog toolset: the consolidated entity CRUD (search/get/create/update/
|
|
3
|
+
* delete across labels, artists, writers, publishers, releases and tracks),
|
|
4
|
+
* image uploads, and the asset read (`get_asset`).
|
|
5
|
+
*
|
|
6
|
+
* Every tool selects its entity via the registry in `src/entities.ts`.
|
|
7
|
+
* Create/update forward a permissive `fields` object straight to the API,
|
|
8
|
+
* which owns all validation — this keeps each tool a thin wrapper and lets the
|
|
9
|
+
* full documented field set through without re-declaring it here. The two
|
|
10
|
+
* reads are [proj] tools: concise-mode projection by default, with
|
|
11
|
+
* `response_format: 'detailed'` returning the verbatim API response.
|
|
12
|
+
*/
|
|
13
|
+
import { statSync } from 'node:fs';
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
import { assertAllowedExtension } from '../api/content-types.js';
|
|
16
|
+
import { ENTITIES, ENTITY_NAMES } from '../entities.js';
|
|
17
|
+
import { applyProjection } from '../projection.js';
|
|
18
|
+
/** Accepted image extensions for the catalog image uploads. */
|
|
19
|
+
const IMAGE_EXTS = ['.jpg', '.jpeg', '.png', '.webp', '.tif', '.tiff'];
|
|
20
|
+
const entityArg = z.enum(ENTITY_NAMES).describe('The catalog entity kind.');
|
|
21
|
+
const idArg = z.number().int().positive().describe('The entity id.');
|
|
22
|
+
const responseFormat = z
|
|
23
|
+
.enum(['concise', 'detailed'])
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("'concise' (default) keeps only the high-signal fields (ids always kept); 'detailed' returns the verbatim API response.");
|
|
26
|
+
/** A permissive body of API fields, forwarded verbatim to the endpoint. */
|
|
27
|
+
function fieldsBody(desc) {
|
|
28
|
+
return z.record(z.string(), z.unknown()).describe(desc);
|
|
29
|
+
}
|
|
30
|
+
/** Optional caller-supplied idempotency key, plumbed to the Idempotency-Key header. */
|
|
31
|
+
const idempotencyKey = z
|
|
32
|
+
.string()
|
|
33
|
+
.min(8)
|
|
34
|
+
.max(128)
|
|
35
|
+
.optional()
|
|
36
|
+
.describe('Honored for release and track only: the server deduplicates by this key for 24h — reuse the SAME key when retrying an unobserved call. Ignored for other entities.');
|
|
37
|
+
/** Rejects a path that is not an existing regular file, before any HTTP call. */
|
|
38
|
+
function fileError(p) {
|
|
39
|
+
let isFile = false;
|
|
40
|
+
try {
|
|
41
|
+
isFile = statSync(p).isFile();
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
isFile = false;
|
|
45
|
+
}
|
|
46
|
+
return isFile
|
|
47
|
+
? null
|
|
48
|
+
: { code: 'FILE_NOT_FOUND', message: `No readable file at ${p}.`, status: 0 };
|
|
49
|
+
}
|
|
50
|
+
function entityDoc(pick) {
|
|
51
|
+
return ENTITY_NAMES.map((name) => pick(ENTITIES[name])).join(' ');
|
|
52
|
+
}
|
|
53
|
+
const searchCatalog = {
|
|
54
|
+
name: 'search_catalog',
|
|
55
|
+
toolset: 'catalog',
|
|
56
|
+
gate: 'read',
|
|
57
|
+
title: 'Search the catalog',
|
|
58
|
+
description: `List catalog entities of one kind, paginated. Pick the kind with \`entity\`: label, artist, writer, publisher, release, or track. \`filters\` takes the endpoint’s own filter names, passed through verbatim — ${entityDoc((s) => s.filtersDoc)} Use get_catalog_item for one entity's full detail. response_format:'detailed' returns the verbatim API response.`,
|
|
59
|
+
inputShape: {
|
|
60
|
+
entity: entityArg,
|
|
61
|
+
filters: z
|
|
62
|
+
.record(z.string(), z.unknown())
|
|
63
|
+
.optional()
|
|
64
|
+
.describe('Filter names → values, passed through verbatim.'),
|
|
65
|
+
page: z.number().int().positive().optional().describe('1-based page number.'),
|
|
66
|
+
per_page: z.number().int().positive().optional().describe('Items per page.'),
|
|
67
|
+
response_format: responseFormat,
|
|
68
|
+
},
|
|
69
|
+
annotations: { readOnlyHint: true },
|
|
70
|
+
handler: async (args, { client }) => {
|
|
71
|
+
const spec = ENTITIES[args.entity];
|
|
72
|
+
const result = await client.get(spec.path, {
|
|
73
|
+
page: args.page,
|
|
74
|
+
per_page: args.per_page,
|
|
75
|
+
filter: args.filters,
|
|
76
|
+
});
|
|
77
|
+
return applyProjection(result, 'search_catalog', args.response_format);
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
const getCatalogItem = {
|
|
81
|
+
name: 'get_catalog_item',
|
|
82
|
+
toolset: 'catalog',
|
|
83
|
+
gate: 'read',
|
|
84
|
+
title: 'Get a catalog item',
|
|
85
|
+
description: 'Retrieve one catalog entity by id, with its full detail — a label’s settings, an artist’s identifiers and links, a writer’s PRO/IPI, a release’s metadata and track listing, a track’s contributors and royalty splits. ' +
|
|
86
|
+
"Pick the kind with `entity`: label, artist, writer, publisher, release, or track. response_format:'detailed' returns the verbatim API response.",
|
|
87
|
+
inputShape: {
|
|
88
|
+
entity: entityArg,
|
|
89
|
+
id: idArg,
|
|
90
|
+
response_format: responseFormat,
|
|
91
|
+
},
|
|
92
|
+
annotations: { readOnlyHint: true },
|
|
93
|
+
handler: async (args, { client }) => {
|
|
94
|
+
const spec = ENTITIES[args.entity];
|
|
95
|
+
const result = await client.get(`${spec.path}/${args.id}`);
|
|
96
|
+
return applyProjection(result, 'get_catalog_item', args.response_format);
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
const createCatalogItem = {
|
|
100
|
+
name: 'create_catalog_item',
|
|
101
|
+
toolset: 'catalog',
|
|
102
|
+
gate: 'safe_write',
|
|
103
|
+
title: 'Create a catalog item',
|
|
104
|
+
description: `Create a catalog entity. Pick the kind with \`entity\` and pass its attributes in \`fields\` — the API owns all validation. Required and common fields per entity: ${entityDoc((s) => s.fieldsDoc)} A release is created in DRAFT state — add tracks, then run the release checks before distributing. \`idempotency_key\` is honored for release and track only.`,
|
|
105
|
+
inputShape: {
|
|
106
|
+
entity: entityArg,
|
|
107
|
+
fields: fieldsBody('The entity attributes, forwarded verbatim.'),
|
|
108
|
+
idempotency_key: idempotencyKey,
|
|
109
|
+
},
|
|
110
|
+
annotations: {},
|
|
111
|
+
handler: (args, { client }) => {
|
|
112
|
+
const entity = args.entity;
|
|
113
|
+
const spec = ENTITIES[entity];
|
|
114
|
+
// Only the release and track POST endpoints support idempotency keys — the
|
|
115
|
+
// header is never sent for the other entities (their endpoints lack it).
|
|
116
|
+
if (entity === 'release' || entity === 'track') {
|
|
117
|
+
return client.post(spec.path, args.fields, {
|
|
118
|
+
idempotency: true,
|
|
119
|
+
idempotencyKey: args.idempotency_key,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return client.post(spec.path, args.fields);
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
const updateCatalogItem = {
|
|
126
|
+
name: 'update_catalog_item',
|
|
127
|
+
toolset: 'catalog',
|
|
128
|
+
gate: 'safe_write',
|
|
129
|
+
title: 'Update a catalog item',
|
|
130
|
+
description: 'Update a catalog entity. Pick the kind with `entity`, supply only the fields you want to change in `fields` (same field sets as create_catalog_item). ' +
|
|
131
|
+
'For releases: 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 fields lock the same way once the parent release is submitted or distributed.',
|
|
132
|
+
inputShape: {
|
|
133
|
+
entity: entityArg,
|
|
134
|
+
id: idArg,
|
|
135
|
+
fields: fieldsBody('The fields to change, forwarded verbatim.'),
|
|
136
|
+
},
|
|
137
|
+
annotations: { idempotentHint: true },
|
|
138
|
+
handler: (args, { client }) => {
|
|
139
|
+
const spec = ENTITIES[args.entity];
|
|
140
|
+
return client.patch(`${spec.path}/${args.id}`, args.fields);
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
const deleteCatalogItem = {
|
|
144
|
+
name: 'delete_catalog_item',
|
|
145
|
+
toolset: 'catalog',
|
|
146
|
+
gate: 'safe_write',
|
|
147
|
+
title: 'Delete a catalog item',
|
|
148
|
+
description: `Delete a catalog entity by id. The API refuses deletes that would orphan data — ${entityDoc((s) => s.deleteNote)}`,
|
|
149
|
+
inputShape: { entity: entityArg, id: idArg },
|
|
150
|
+
annotations: { destructiveHint: true },
|
|
151
|
+
handler: (args, { client }) => {
|
|
152
|
+
const spec = ENTITIES[args.entity];
|
|
153
|
+
return client.delete(`${spec.path}/${args.id}`);
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
/** Maps the label image targets to the API's imageType path segment. */
|
|
157
|
+
const LABEL_IMAGE_TYPES = {
|
|
158
|
+
label_logo: 'logo',
|
|
159
|
+
label_logo_dark: 'logo-dark',
|
|
160
|
+
label_background: 'background',
|
|
161
|
+
};
|
|
162
|
+
const uploadImage = {
|
|
163
|
+
name: 'upload_image',
|
|
164
|
+
toolset: 'catalog',
|
|
165
|
+
gate: 'safe_write',
|
|
166
|
+
title: 'Upload a catalog image',
|
|
167
|
+
description: 'Upload a label image or an artist photo from a local file. `target`: label_logo, label_logo_dark (a dark-mode variant), label_background, or artist_photo. `id` is the label id for label_* and the artist id for artist_photo. `file_path` must be a local image file.',
|
|
168
|
+
inputShape: {
|
|
169
|
+
target: z
|
|
170
|
+
.enum(['label_logo', 'label_logo_dark', 'label_background', 'artist_photo'])
|
|
171
|
+
.describe('Which image asset to upload.'),
|
|
172
|
+
id: z.number().int().positive().describe('The label id (label_*) or artist id (artist_photo).'),
|
|
173
|
+
file_path: z.string().describe('Local path to the image file to upload.'),
|
|
174
|
+
},
|
|
175
|
+
annotations: {},
|
|
176
|
+
handler: async (args, { client }) => {
|
|
177
|
+
const err = fileError(args.file_path);
|
|
178
|
+
if (err)
|
|
179
|
+
return { error: err };
|
|
180
|
+
const ext = assertAllowedExtension(args.file_path, IMAGE_EXTS);
|
|
181
|
+
if ('error' in ext)
|
|
182
|
+
return { error: ext.error };
|
|
183
|
+
const target = args.target;
|
|
184
|
+
if (target === 'artist_photo') {
|
|
185
|
+
return client.postMultipart(`/artists/${args.id}/photo`, ext.realPath, 'file');
|
|
186
|
+
}
|
|
187
|
+
return client.postMultipart(`/labels/${args.id}/images/${LABEL_IMAGE_TYPES[target]}`, ext.realPath, 'file');
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
/** The (mode, parent) → allowed-assets matrix — the only combinations with an endpoint. */
|
|
191
|
+
const INFO_TRACK_ASSETS = new Set(['stereo', 'dolby', 'lyrics']);
|
|
192
|
+
const INFO_RELEASE_ASSETS = new Set(['square', 'tall']);
|
|
193
|
+
const DOWNLOAD_TRACK_ASSETS = new Set([
|
|
194
|
+
'audio_16',
|
|
195
|
+
'audio_24',
|
|
196
|
+
'audio_32',
|
|
197
|
+
'audio_preview_full',
|
|
198
|
+
'audio_preview_clip',
|
|
199
|
+
]);
|
|
200
|
+
const GET_ASSET_VALID_COMBINATIONS = 'Valid combinations: mode=info + parent=track + asset stereo|dolby|lyrics (file metadata and processing state); ' +
|
|
201
|
+
'mode=info + parent=release + asset square|tall (animated cover / motion artwork video metadata); ' +
|
|
202
|
+
'mode=download_url + parent=track + asset audio_16|audio_24|audio_32|audio_preview_full|audio_preview_clip (a signed download URL).';
|
|
203
|
+
const getAsset = {
|
|
204
|
+
name: 'get_asset',
|
|
205
|
+
toolset: 'catalog',
|
|
206
|
+
gate: 'read',
|
|
207
|
+
title: 'Get an asset',
|
|
208
|
+
description: 'Read a track or release asset. Valid selector matrices: ' +
|
|
209
|
+
"(1) mode='info', parent='track', asset stereo|dolby|lyrics — file metadata (not the bytes) incl. processing state. " +
|
|
210
|
+
"(2) mode='info', parent='release', asset square|tall — animated cover (motion artwork) video metadata incl. processing state. " +
|
|
211
|
+
"(3) mode='download_url', parent='track', asset audio_16|audio_24|audio_32 (WAV master at that bit depth) or audio_preview_full|audio_preview_clip (generated 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. " +
|
|
212
|
+
'Any other combination has no endpoint and returns a structured error. mode defaults to info.',
|
|
213
|
+
inputShape: {
|
|
214
|
+
parent: z.enum(['track', 'release']).describe('Whose asset.'),
|
|
215
|
+
id: z.number().int().positive().describe('The track id or release id, per `parent`.'),
|
|
216
|
+
asset: z
|
|
217
|
+
.enum([
|
|
218
|
+
'stereo',
|
|
219
|
+
'dolby',
|
|
220
|
+
'lyrics',
|
|
221
|
+
'square',
|
|
222
|
+
'tall',
|
|
223
|
+
'audio_16',
|
|
224
|
+
'audio_24',
|
|
225
|
+
'audio_32',
|
|
226
|
+
'audio_preview_full',
|
|
227
|
+
'audio_preview_clip',
|
|
228
|
+
])
|
|
229
|
+
.describe('Which asset — see the description for the valid parent/mode pairings.'),
|
|
230
|
+
mode: z
|
|
231
|
+
.enum(['info', 'download_url'])
|
|
232
|
+
.optional()
|
|
233
|
+
.describe('info (default) returns file metadata; download_url returns a signed URL.'),
|
|
234
|
+
},
|
|
235
|
+
annotations: { readOnlyHint: true },
|
|
236
|
+
handler: (args, { client }) => {
|
|
237
|
+
const mode = args.mode ?? 'info';
|
|
238
|
+
const parent = args.parent;
|
|
239
|
+
const asset = args.asset;
|
|
240
|
+
if (mode === 'info' && parent === 'track' && INFO_TRACK_ASSETS.has(asset)) {
|
|
241
|
+
return client.get(`/tracks/${args.id}/files/${asset}`);
|
|
242
|
+
}
|
|
243
|
+
if (mode === 'info' && parent === 'release' && INFO_RELEASE_ASSETS.has(asset)) {
|
|
244
|
+
return client.get(`/releases/${args.id}/files/${asset}`);
|
|
245
|
+
}
|
|
246
|
+
if (mode === 'download_url' && parent === 'track' && DOWNLOAD_TRACK_ASSETS.has(asset)) {
|
|
247
|
+
return client.get(`/tracks/${args.id}/files/${asset}/download-url`);
|
|
248
|
+
}
|
|
249
|
+
// No endpoint exists for this combination — there is nothing to send it to.
|
|
250
|
+
return Promise.resolve({
|
|
251
|
+
error: {
|
|
252
|
+
code: 'INVALID_SELECTOR',
|
|
253
|
+
message: `No endpoint exists for mode=${mode}, parent=${parent}, asset=${asset}. ${GET_ASSET_VALID_COMBINATIONS}`,
|
|
254
|
+
status: 0,
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
export const catalogTools = [
|
|
260
|
+
searchCatalog,
|
|
261
|
+
getCatalogItem,
|
|
262
|
+
createCatalogItem,
|
|
263
|
+
updateCatalogItem,
|
|
264
|
+
deleteCatalogItem,
|
|
265
|
+
uploadImage,
|
|
266
|
+
getAsset,
|
|
267
|
+
];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Distribution toolset (full writes): 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/motion-artwork uploads (via the
|
|
8
|
+
* presigned-URL flow or multipart), license file management, the FINAL
|
|
9
|
+
* distribute/takedown actions, the Preflight-QC confirm-review step, and
|
|
10
|
+
* one-time Beatport onboarding.
|
|
11
|
+
*/
|
|
12
|
+
import type { ToolDef } from './types.js';
|
|
13
|
+
export declare const distributionTools: ToolDef[];
|