@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/coverage.js
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* uppercase method followed by a single space.
|
|
16
16
|
*/
|
|
17
17
|
export const COVERAGE = {
|
|
18
|
-
//
|
|
19
|
-
'GET /me': '
|
|
18
|
+
// account
|
|
19
|
+
'GET /me': 'get_account',
|
|
20
20
|
'DELETE /tokens/current': 'revoke_api_token',
|
|
21
21
|
'DELETE /tokens/{tokenId}': 'revoke_api_token',
|
|
22
22
|
// reference
|
|
@@ -27,93 +27,93 @@ export const COVERAGE = {
|
|
|
27
27
|
'GET /instruments': 'list_reference_data',
|
|
28
28
|
'GET /distro-outlets': 'list_reference_data',
|
|
29
29
|
'GET /territories': 'list_reference_data',
|
|
30
|
-
//
|
|
30
|
+
// insights
|
|
31
31
|
'GET /analytics/summary': 'get_analytics',
|
|
32
32
|
// catalog reads
|
|
33
|
-
'GET /labels': '
|
|
34
|
-
'GET /labels/{label}': '
|
|
35
|
-
'GET /artists': '
|
|
36
|
-
'GET /artists/{artist}': '
|
|
37
|
-
'GET /writers': '
|
|
38
|
-
'GET /writers/{writer}': '
|
|
39
|
-
'GET /publishers': '
|
|
40
|
-
'GET /publishers/{publisher}': '
|
|
41
|
-
'GET /releases': '
|
|
42
|
-
'GET /releases/{release}': '
|
|
43
|
-
'GET /tracks': '
|
|
44
|
-
'GET /tracks/{track}': '
|
|
45
|
-
//
|
|
46
|
-
'GET /tracks/{track}/files/{fileType}': '
|
|
33
|
+
'GET /labels': 'search_catalog',
|
|
34
|
+
'GET /labels/{label}': 'get_catalog_item',
|
|
35
|
+
'GET /artists': 'search_catalog',
|
|
36
|
+
'GET /artists/{artist}': 'get_catalog_item',
|
|
37
|
+
'GET /writers': 'search_catalog',
|
|
38
|
+
'GET /writers/{writer}': 'get_catalog_item',
|
|
39
|
+
'GET /publishers': 'search_catalog',
|
|
40
|
+
'GET /publishers/{publisher}': 'get_catalog_item',
|
|
41
|
+
'GET /releases': 'search_catalog',
|
|
42
|
+
'GET /releases/{release}': 'get_catalog_item',
|
|
43
|
+
'GET /tracks': 'search_catalog',
|
|
44
|
+
'GET /tracks/{track}': 'get_catalog_item',
|
|
45
|
+
// asset reads
|
|
46
|
+
'GET /tracks/{track}/files/{fileType}': 'get_asset',
|
|
47
47
|
'GET /tracks/{track}/licenses': 'list_track_licenses',
|
|
48
|
-
'GET /tracks/{track}/licenses/{trackLicense}': '
|
|
49
|
-
'GET /releases/{release}/files/{assetType}': '
|
|
50
|
-
// review reads
|
|
51
|
-
'GET /review-issues': '
|
|
52
|
-
'GET /issue-definitions': '
|
|
53
|
-
'GET /releases/{release}/quality-report': '
|
|
54
|
-
'GET /stream-radar/flags': '
|
|
55
|
-
'GET /stream-radar/flags/{streamRadarFlag}': '
|
|
48
|
+
'GET /tracks/{track}/licenses/{trackLicense}': 'list_track_licenses',
|
|
49
|
+
'GET /releases/{release}/files/{assetType}': 'get_asset',
|
|
50
|
+
// release review reads
|
|
51
|
+
'GET /review-issues': 'get_release_review',
|
|
52
|
+
'GET /issue-definitions': 'list_reference_data',
|
|
53
|
+
'GET /releases/{release}/quality-report': 'get_release_review',
|
|
54
|
+
'GET /stream-radar/flags': 'query_artificial_streaming',
|
|
55
|
+
'GET /stream-radar/flags/{streamRadarFlag}': 'query_artificial_streaming',
|
|
56
56
|
// delivery
|
|
57
57
|
'GET /queues/distro': 'get_delivery_queue',
|
|
58
58
|
'GET /releases/{release}/landing-config': 'get_landing_config',
|
|
59
|
-
//
|
|
60
|
-
'GET /statements': '
|
|
61
|
-
'GET /statements/{invoiceNumber}': '
|
|
62
|
-
'GET /statements/{invoiceNumber}/csv': '
|
|
63
|
-
'GET /statements/export/csv': '
|
|
64
|
-
'GET /statements/{invoiceNumber}/invoice': '
|
|
65
|
-
'GET /transactions': '
|
|
66
|
-
'GET /royalties/breakdown': '
|
|
67
|
-
'GET /royalties/artificial-streams': '
|
|
68
|
-
'GET /artificial-streaming-fee/{period}': '
|
|
59
|
+
// finance
|
|
60
|
+
'GET /statements': 'query_financials',
|
|
61
|
+
'GET /statements/{invoiceNumber}': 'query_financials',
|
|
62
|
+
'GET /statements/{invoiceNumber}/csv': 'download_statement',
|
|
63
|
+
'GET /statements/export/csv': 'download_statement',
|
|
64
|
+
'GET /statements/{invoiceNumber}/invoice': 'download_statement',
|
|
65
|
+
'GET /transactions': 'query_financials',
|
|
66
|
+
'GET /royalties/breakdown': 'query_financials',
|
|
67
|
+
'GET /royalties/artificial-streams': 'query_artificial_streaming',
|
|
68
|
+
'GET /artificial-streaming-fee/{period}': 'query_artificial_streaming',
|
|
69
69
|
// webhooks
|
|
70
70
|
'GET /webhooks': 'list_webhooks',
|
|
71
|
-
'POST /webhooks': '
|
|
72
|
-
'GET /webhooks/event-types': '
|
|
73
|
-
'GET /webhooks/{webhook}': '
|
|
74
|
-
'PATCH /webhooks/{webhook}': '
|
|
75
|
-
'DELETE /webhooks/{webhook}': '
|
|
76
|
-
'GET /webhooks/{webhook}/logs': '
|
|
77
|
-
'POST /webhooks/{webhook}/regenerate-secret': '
|
|
78
|
-
'POST /webhooks/{webhook}/test': '
|
|
71
|
+
'POST /webhooks': 'manage_webhook',
|
|
72
|
+
'GET /webhooks/event-types': 'list_reference_data',
|
|
73
|
+
'GET /webhooks/{webhook}': 'list_webhooks',
|
|
74
|
+
'PATCH /webhooks/{webhook}': 'manage_webhook',
|
|
75
|
+
'DELETE /webhooks/{webhook}': 'manage_webhook',
|
|
76
|
+
'GET /webhooks/{webhook}/logs': 'list_webhooks',
|
|
77
|
+
'POST /webhooks/{webhook}/regenerate-secret': 'manage_webhook',
|
|
78
|
+
'POST /webhooks/{webhook}/test': 'manage_webhook',
|
|
79
79
|
// catalog writes
|
|
80
|
-
'POST /labels': '
|
|
81
|
-
'PATCH /labels/{label}': '
|
|
82
|
-
'DELETE /labels/{label}': '
|
|
83
|
-
'POST /labels/{label}/images/{imageType}': '
|
|
84
|
-
'POST /artists': '
|
|
85
|
-
'PATCH /artists/{artist}': '
|
|
86
|
-
'DELETE /artists/{artist}': '
|
|
87
|
-
'POST /artists/{artist}/photo': '
|
|
88
|
-
'POST /writers': '
|
|
89
|
-
'PATCH /writers/{writer}': '
|
|
90
|
-
'DELETE /writers/{writer}': '
|
|
91
|
-
'POST /publishers': '
|
|
92
|
-
'PATCH /publishers/{publisher}': '
|
|
93
|
-
'DELETE /publishers/{publisher}': '
|
|
80
|
+
'POST /labels': 'create_catalog_item',
|
|
81
|
+
'PATCH /labels/{label}': 'update_catalog_item',
|
|
82
|
+
'DELETE /labels/{label}': 'delete_catalog_item',
|
|
83
|
+
'POST /labels/{label}/images/{imageType}': 'upload_image',
|
|
84
|
+
'POST /artists': 'create_catalog_item',
|
|
85
|
+
'PATCH /artists/{artist}': 'update_catalog_item',
|
|
86
|
+
'DELETE /artists/{artist}': 'delete_catalog_item',
|
|
87
|
+
'POST /artists/{artist}/photo': 'upload_image',
|
|
88
|
+
'POST /writers': 'create_catalog_item',
|
|
89
|
+
'PATCH /writers/{writer}': 'update_catalog_item',
|
|
90
|
+
'DELETE /writers/{writer}': 'delete_catalog_item',
|
|
91
|
+
'POST /publishers': 'create_catalog_item',
|
|
92
|
+
'PATCH /publishers/{publisher}': 'update_catalog_item',
|
|
93
|
+
'DELETE /publishers/{publisher}': 'delete_catalog_item',
|
|
94
94
|
// release/track draft writes
|
|
95
|
-
'POST /releases': '
|
|
96
|
-
'PATCH /releases/{release}': '
|
|
97
|
-
'DELETE /releases/{release}': '
|
|
98
|
-
'POST /tracks': '
|
|
99
|
-
'PATCH /tracks/{track}': '
|
|
100
|
-
'DELETE /tracks/{track}': '
|
|
101
|
-
'POST /releases/{release}/validate': '
|
|
102
|
-
'POST /releases/{release}/quality-report/refresh': '
|
|
103
|
-
'PUT /releases/{release}/landing-config': '
|
|
104
|
-
'POST /releases/short-url': '
|
|
95
|
+
'POST /releases': 'create_catalog_item',
|
|
96
|
+
'PATCH /releases/{release}': 'update_catalog_item',
|
|
97
|
+
'DELETE /releases/{release}': 'delete_catalog_item',
|
|
98
|
+
'POST /tracks': 'create_catalog_item',
|
|
99
|
+
'PATCH /tracks/{track}': 'update_catalog_item',
|
|
100
|
+
'DELETE /tracks/{track}': 'delete_catalog_item',
|
|
101
|
+
'POST /releases/{release}/validate': 'run_release_checks',
|
|
102
|
+
'POST /releases/{release}/quality-report/refresh': 'run_release_checks',
|
|
103
|
+
'PUT /releases/{release}/landing-config': 'manage_release_links',
|
|
104
|
+
'POST /releases/short-url': 'manage_release_links',
|
|
105
105
|
'POST /review-issues/{reviewReleaseIssue}/notes': 'add_review_issue_note',
|
|
106
106
|
// full writes (distribution)
|
|
107
|
-
'POST /tracks/{track}/files/{fileType}/upload-url': '
|
|
108
|
-
'PUT /tracks/{track}/files/{fileType}': '
|
|
109
|
-
'DELETE /tracks/{track}/files/{fileType}': '
|
|
110
|
-
'POST /releases/{release}/files/{assetType}/upload-url': '
|
|
111
|
-
'PUT /releases/{release}/files/{assetType}': '
|
|
112
|
-
'DELETE /releases/{release}/files/{assetType}': '
|
|
113
|
-
'POST /tracks/{track}/licenses': '
|
|
114
|
-
'POST /tracks/{track}/licenses/{trackLicense}': '
|
|
115
|
-
'DELETE /tracks/{track}/licenses/{trackLicense}': '
|
|
116
|
-
'POST /releases/{release}/photo': '
|
|
107
|
+
'POST /tracks/{track}/files/{fileType}/upload-url': 'upload_asset',
|
|
108
|
+
'PUT /tracks/{track}/files/{fileType}': 'upload_asset',
|
|
109
|
+
'DELETE /tracks/{track}/files/{fileType}': 'delete_asset',
|
|
110
|
+
'POST /releases/{release}/files/{assetType}/upload-url': 'upload_asset',
|
|
111
|
+
'PUT /releases/{release}/files/{assetType}': 'upload_asset',
|
|
112
|
+
'DELETE /releases/{release}/files/{assetType}': 'delete_asset',
|
|
113
|
+
'POST /tracks/{track}/licenses': 'manage_track_license',
|
|
114
|
+
'POST /tracks/{track}/licenses/{trackLicense}': 'manage_track_license',
|
|
115
|
+
'DELETE /tracks/{track}/licenses/{trackLicense}': 'manage_track_license',
|
|
116
|
+
'POST /releases/{release}/photo': 'upload_asset',
|
|
117
117
|
'POST /releases/{release}/distribute': 'distribute_release',
|
|
118
118
|
'POST /releases/{release}/takedown-all': 'takedown_release',
|
|
119
119
|
'POST /releases/{release}/confirm-review': 'confirm_review',
|
|
@@ -145,6 +145,6 @@ export const EXCLUDED = {
|
|
|
145
145
|
'GET /transactions/csv': 'transaction CSV export — not exposed in v1',
|
|
146
146
|
};
|
|
147
147
|
export const PENDING_DOCS = {
|
|
148
|
-
'GET /account': '
|
|
149
|
-
'GET /tracks/{track}/files/{assetType}/download-url': '
|
|
148
|
+
'GET /account': 'get_account',
|
|
149
|
+
'GET /tracks/{track}/files/{assetType}/download-url': 'get_asset',
|
|
150
150
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The catalog-entity registry: the six entity kinds the consolidated catalog
|
|
3
|
+
* tools operate on, each with its endpoint path and the reviewed documentation
|
|
4
|
+
* fragments (list filters, create/update fields, delete refusals) the tool
|
|
5
|
+
* descriptions are assembled from.
|
|
6
|
+
*
|
|
7
|
+
* This is data, not behavior — the catalog tools stay thin wrappers and the
|
|
8
|
+
* API owns all validation. The wording here carries the caveats from the
|
|
9
|
+
* per-entity tool descriptions it replaces (recording_country on track create,
|
|
10
|
+
* RELEASE_LOCKED_FIELDS on release update, the delete refusals).
|
|
11
|
+
*/
|
|
12
|
+
export type EntityName = 'label' | 'artist' | 'writer' | 'publisher' | 'release' | 'track';
|
|
13
|
+
/** The entity names as a tuple, for zod enum inputs. */
|
|
14
|
+
export declare const ENTITY_NAMES: readonly ["label", "artist", "writer", "publisher", "release", "track"];
|
|
15
|
+
export type EntitySpec = {
|
|
16
|
+
/** The collection endpoint path, e.g. '/labels'. */
|
|
17
|
+
path: string;
|
|
18
|
+
/** One-line doc of the useful list filters for search_catalog. */
|
|
19
|
+
filtersDoc: string;
|
|
20
|
+
/** One-line doc of required + common create/update fields. */
|
|
21
|
+
fieldsDoc: string;
|
|
22
|
+
/** One-line doc of the server-side delete refusals. */
|
|
23
|
+
deleteNote: string;
|
|
24
|
+
};
|
|
25
|
+
export declare const ENTITIES: Record<EntityName, EntitySpec>;
|
package/dist/entities.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The catalog-entity registry: the six entity kinds the consolidated catalog
|
|
3
|
+
* tools operate on, each with its endpoint path and the reviewed documentation
|
|
4
|
+
* fragments (list filters, create/update fields, delete refusals) the tool
|
|
5
|
+
* descriptions are assembled from.
|
|
6
|
+
*
|
|
7
|
+
* This is data, not behavior — the catalog tools stay thin wrappers and the
|
|
8
|
+
* API owns all validation. The wording here carries the caveats from the
|
|
9
|
+
* per-entity tool descriptions it replaces (recording_country on track create,
|
|
10
|
+
* RELEASE_LOCKED_FIELDS on release update, the delete refusals).
|
|
11
|
+
*/
|
|
12
|
+
/** The entity names as a tuple, for zod enum inputs. */
|
|
13
|
+
export const ENTITY_NAMES = ['label', 'artist', 'writer', 'publisher', 'release', 'track'];
|
|
14
|
+
export const ENTITIES = {
|
|
15
|
+
label: {
|
|
16
|
+
path: '/labels',
|
|
17
|
+
filtersDoc: 'label: no documented filters — paginate with page/per_page.',
|
|
18
|
+
fieldsDoc: 'label — required: name, default_email; optional: support email, website/platform URLs, default copyright lines, isrc_base.',
|
|
19
|
+
deleteNote: 'label: refused while the label still has releases — remove or reassign its releases first.',
|
|
20
|
+
},
|
|
21
|
+
artist: {
|
|
22
|
+
path: '/artists',
|
|
23
|
+
filtersDoc: 'artist: artist_name (filter by artist name).',
|
|
24
|
+
fieldsDoc: 'artist — required: artist_name; optional: full_name, email, location, bios, isni, default_language, platform profile URLs.',
|
|
25
|
+
deleteNote: 'artist: refused while still referenced by releases or tracks.',
|
|
26
|
+
},
|
|
27
|
+
writer: {
|
|
28
|
+
path: '/writers',
|
|
29
|
+
filtersDoc: 'writer: name (writer name), ipi (IPI number).',
|
|
30
|
+
fieldsDoc: '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).',
|
|
31
|
+
deleteNote: 'writer: refused while still referenced by tracks.',
|
|
32
|
+
},
|
|
33
|
+
publisher: {
|
|
34
|
+
path: '/publishers',
|
|
35
|
+
filtersDoc: 'publisher: name (publisher name), ipi (IPI number).',
|
|
36
|
+
fieldsDoc: 'publisher — required: name; optional: ipi, pro, isni, controlled_publisher.',
|
|
37
|
+
deleteNote: 'publisher: refused while still referenced by writers.',
|
|
38
|
+
},
|
|
39
|
+
release: {
|
|
40
|
+
path: '/releases',
|
|
41
|
+
filtersDoc: 'release: label_id (owning label id), is_live (1 = live/distributed only), barcode_number (UPC/EAN), cat (catalog number).',
|
|
42
|
+
fieldsDoc: '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.',
|
|
43
|
+
deleteNote: 'release: only a never-submitted draft can be deleted.',
|
|
44
|
+
},
|
|
45
|
+
track: {
|
|
46
|
+
path: '/tracks',
|
|
47
|
+
filtersDoc: 'track: release_id (one release’s tracks), isrc (filter by ISRC).',
|
|
48
|
+
fieldsDoc: '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.',
|
|
49
|
+
deleteNote: 'track: allowed while the parent release is an editable draft; refused once submitted or distributed.',
|
|
50
|
+
},
|
|
51
|
+
};
|
package/dist/gating.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* class is armed. Anything the matrix cannot positively resolve — including an
|
|
6
6
|
* unrecognized gate — is disabled.
|
|
7
7
|
*/
|
|
8
|
-
import type
|
|
8
|
+
import { type Config } from './config.js';
|
|
9
9
|
export type Gate = 'read' | 'safe_write' | 'full_write';
|
|
10
10
|
export declare function isToolEnabled(t: {
|
|
11
11
|
gate: Gate;
|
package/dist/gating.js
CHANGED
|
@@ -5,8 +5,12 @@
|
|
|
5
5
|
* class is armed. Anything the matrix cannot positively resolve — including an
|
|
6
6
|
* unrecognized gate — is disabled.
|
|
7
7
|
*/
|
|
8
|
+
import { defaultExcludedToolsets } from './config.js';
|
|
8
9
|
export function isToolEnabled(t, c) {
|
|
9
|
-
|
|
10
|
+
// With no explicit LABELGRID_TOOLSETS selection, the default surface applies
|
|
11
|
+
// (which excludes the default-off toolsets, e.g. webhooks). An explicit
|
|
12
|
+
// selection wins: naming a default-off toolset enables it.
|
|
13
|
+
const toolsetSelected = c.toolsets === null ? !defaultExcludedToolsets.has(t.toolset) : c.toolsets.has(t.toolset);
|
|
10
14
|
if (!toolsetSelected)
|
|
11
15
|
return false;
|
|
12
16
|
switch (t.gate) {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Concise-mode field projection.
|
|
3
|
+
*
|
|
4
|
+
* Tools marked [proj] accept `response_format: 'concise' | 'detailed'`
|
|
5
|
+
* (default concise). Concise mode projects the API response down to a per-tool
|
|
6
|
+
* allowlist of high-signal fields so large payloads stop swamping the client's
|
|
7
|
+
* context window; `'detailed'` bypasses projection and returns the verbatim
|
|
8
|
+
* API response.
|
|
9
|
+
*
|
|
10
|
+
* The walk keeps, on every object: any key in the per-tool allowlist, ALWAYS
|
|
11
|
+
* `id` and any key ending `_id`, and any key whose value is an object/array
|
|
12
|
+
* (containers are what the walk traverses — this is what keeps pagination
|
|
13
|
+
* envelopes like `{ data: [...] }` intact while their leaves are filtered).
|
|
14
|
+
* Kept values are recursed into. A `"_projection": "concise"` marker is
|
|
15
|
+
* appended at the TOP level only (when the top level is an object).
|
|
16
|
+
*
|
|
17
|
+
* Pagination fidelity: a subtree keyed exactly `meta` or `links` (at ANY depth)
|
|
18
|
+
* is preserved VERBATIM — every field, unfiltered — so page counts, totals and
|
|
19
|
+
* next/prev links always survive. Top-level primitive cursor keys
|
|
20
|
+
* (`next_cursor`, `prev_cursor`, `cursor`) are likewise preserved so
|
|
21
|
+
* cursor-paginated responses stay pageable under concise mode.
|
|
22
|
+
*
|
|
23
|
+
* Projection is presentation-only — it never transforms values. The 400K
|
|
24
|
+
* toToolResult cap stays as the backstop for anything projection cannot tame.
|
|
25
|
+
*/
|
|
26
|
+
import type { ApiResult } from './api/http.js';
|
|
27
|
+
/** Per-tool concise-mode field allowlists, keyed by tool name. */
|
|
28
|
+
export declare const CONCISE_ALLOWLISTS: Record<string, readonly string[]>;
|
|
29
|
+
/**
|
|
30
|
+
* Projects `value` down to the allowlisted fields (plus `id`/`*_id` and
|
|
31
|
+
* container keys), appending the `_projection: 'concise'` marker at the top
|
|
32
|
+
* level when the top level is an object.
|
|
33
|
+
*/
|
|
34
|
+
export declare function projectConcise(value: unknown, keep: readonly string[]): unknown;
|
|
35
|
+
/**
|
|
36
|
+
* Applies a [proj] tool's concise projection to a successful result. Errors
|
|
37
|
+
* pass through untouched; `response_format: 'detailed'` bypasses projection
|
|
38
|
+
* entirely (the verbatim API response); absent or `'concise'` projects.
|
|
39
|
+
*/
|
|
40
|
+
export declare function applyProjection(result: ApiResult<unknown>, toolName: string, responseFormat: unknown): ApiResult<unknown>;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Concise-mode field projection.
|
|
3
|
+
*
|
|
4
|
+
* Tools marked [proj] accept `response_format: 'concise' | 'detailed'`
|
|
5
|
+
* (default concise). Concise mode projects the API response down to a per-tool
|
|
6
|
+
* allowlist of high-signal fields so large payloads stop swamping the client's
|
|
7
|
+
* context window; `'detailed'` bypasses projection and returns the verbatim
|
|
8
|
+
* API response.
|
|
9
|
+
*
|
|
10
|
+
* The walk keeps, on every object: any key in the per-tool allowlist, ALWAYS
|
|
11
|
+
* `id` and any key ending `_id`, and any key whose value is an object/array
|
|
12
|
+
* (containers are what the walk traverses — this is what keeps pagination
|
|
13
|
+
* envelopes like `{ data: [...] }` intact while their leaves are filtered).
|
|
14
|
+
* Kept values are recursed into. A `"_projection": "concise"` marker is
|
|
15
|
+
* appended at the TOP level only (when the top level is an object).
|
|
16
|
+
*
|
|
17
|
+
* Pagination fidelity: a subtree keyed exactly `meta` or `links` (at ANY depth)
|
|
18
|
+
* is preserved VERBATIM — every field, unfiltered — so page counts, totals and
|
|
19
|
+
* next/prev links always survive. Top-level primitive cursor keys
|
|
20
|
+
* (`next_cursor`, `prev_cursor`, `cursor`) are likewise preserved so
|
|
21
|
+
* cursor-paginated responses stay pageable under concise mode.
|
|
22
|
+
*
|
|
23
|
+
* Projection is presentation-only — it never transforms values. The 400K
|
|
24
|
+
* toToolResult cap stays as the backstop for anything projection cannot tame.
|
|
25
|
+
*/
|
|
26
|
+
/** The shared allowlist for catalog entity reads (search_catalog / get_catalog_item). */
|
|
27
|
+
const CATALOG_FIELDS = [
|
|
28
|
+
'title',
|
|
29
|
+
'name',
|
|
30
|
+
'artist_name',
|
|
31
|
+
'full_name',
|
|
32
|
+
'status',
|
|
33
|
+
'review_status',
|
|
34
|
+
'is_live',
|
|
35
|
+
'barcode_number',
|
|
36
|
+
'cat',
|
|
37
|
+
'isrc',
|
|
38
|
+
'release_date',
|
|
39
|
+
'created_at',
|
|
40
|
+
'updated_at',
|
|
41
|
+
'email',
|
|
42
|
+
'ipi',
|
|
43
|
+
'pro',
|
|
44
|
+
];
|
|
45
|
+
/** Per-tool concise-mode field allowlists, keyed by tool name. */
|
|
46
|
+
export const CONCISE_ALLOWLISTS = {
|
|
47
|
+
search_catalog: CATALOG_FIELDS,
|
|
48
|
+
get_catalog_item: CATALOG_FIELDS,
|
|
49
|
+
get_release_review: [
|
|
50
|
+
'code',
|
|
51
|
+
'title',
|
|
52
|
+
'severity',
|
|
53
|
+
'status',
|
|
54
|
+
'requires_feedback',
|
|
55
|
+
'message',
|
|
56
|
+
'created_at',
|
|
57
|
+
],
|
|
58
|
+
get_delivery_queue: ['status', 'outlet', 'outlet_id', 'delivered_at', 'created_at', 'type'],
|
|
59
|
+
query_artificial_streaming: [
|
|
60
|
+
'dsp',
|
|
61
|
+
'country',
|
|
62
|
+
'quantity',
|
|
63
|
+
'period',
|
|
64
|
+
'date',
|
|
65
|
+
'status',
|
|
66
|
+
'severity',
|
|
67
|
+
'isrc',
|
|
68
|
+
'upc',
|
|
69
|
+
],
|
|
70
|
+
query_financials: [
|
|
71
|
+
'period',
|
|
72
|
+
'status',
|
|
73
|
+
'currency',
|
|
74
|
+
'gross_usd',
|
|
75
|
+
'net_usd',
|
|
76
|
+
'amount',
|
|
77
|
+
'total_due_usd',
|
|
78
|
+
'invoice_number',
|
|
79
|
+
'transaction_type',
|
|
80
|
+
'scope',
|
|
81
|
+
'date_paid',
|
|
82
|
+
'created_at',
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
/** Subtrees whose entire contents are kept verbatim, at any depth. */
|
|
86
|
+
const VERBATIM_ENVELOPE_KEYS = new Set(['meta', 'links']);
|
|
87
|
+
/** Primitive cursor keys preserved at the TOP level only. */
|
|
88
|
+
const TOP_LEVEL_CURSOR_KEYS = new Set([
|
|
89
|
+
'next_cursor',
|
|
90
|
+
'prev_cursor',
|
|
91
|
+
'cursor',
|
|
92
|
+
]);
|
|
93
|
+
function isContainer(value) {
|
|
94
|
+
return value !== null && typeof value === 'object';
|
|
95
|
+
}
|
|
96
|
+
function projectValue(value, keep, topLevel = false) {
|
|
97
|
+
if (Array.isArray(value)) {
|
|
98
|
+
return value.map((item) => projectValue(item, keep));
|
|
99
|
+
}
|
|
100
|
+
if (isContainer(value)) {
|
|
101
|
+
const out = {};
|
|
102
|
+
for (const [key, item] of Object.entries(value)) {
|
|
103
|
+
// Pagination envelopes: a `meta`/`links` subtree is kept verbatim, unfiltered.
|
|
104
|
+
if (VERBATIM_ENVELOPE_KEYS.has(key)) {
|
|
105
|
+
out[key] = item;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
// Top-level primitive cursor keys survive so pagination isn't lost.
|
|
109
|
+
if (topLevel && TOP_LEVEL_CURSOR_KEYS.has(key) && !isContainer(item)) {
|
|
110
|
+
out[key] = item;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (keep.has(key) || key === 'id' || key.endsWith('_id') || isContainer(item)) {
|
|
114
|
+
out[key] = isContainer(item) ? projectValue(item, keep) : item;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Projects `value` down to the allowlisted fields (plus `id`/`*_id` and
|
|
123
|
+
* container keys), appending the `_projection: 'concise'` marker at the top
|
|
124
|
+
* level when the top level is an object.
|
|
125
|
+
*/
|
|
126
|
+
export function projectConcise(value, keep) {
|
|
127
|
+
const keepSet = new Set(keep);
|
|
128
|
+
const projected = projectValue(value, keepSet, true);
|
|
129
|
+
if (isContainer(projected) && !Array.isArray(projected)) {
|
|
130
|
+
return { ...projected, _projection: 'concise' };
|
|
131
|
+
}
|
|
132
|
+
return projected;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Applies a [proj] tool's concise projection to a successful result. Errors
|
|
136
|
+
* pass through untouched; `response_format: 'detailed'` bypasses projection
|
|
137
|
+
* entirely (the verbatim API response); absent or `'concise'` projects.
|
|
138
|
+
*/
|
|
139
|
+
export function applyProjection(result, toolName, responseFormat) {
|
|
140
|
+
if (responseFormat === 'detailed')
|
|
141
|
+
return result;
|
|
142
|
+
if ('error' in result)
|
|
143
|
+
return result;
|
|
144
|
+
return { data: projectConcise(result.data, CONCISE_ALLOWLISTS[toolName] ?? []) };
|
|
145
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP resources: the nine reference datasets, exposed at
|
|
3
|
+
* `labelgrid://reference/{type}`.
|
|
4
|
+
*
|
|
5
|
+
* Each read fetches the dataset via the shared client (no caching) and returns
|
|
6
|
+
* JSON text. The same datasets are served by the `list_reference_data` tool,
|
|
7
|
+
* which is the fallback for clients that don't surface resources — the
|
|
8
|
+
* type→path map lives here as the single source for both. In setup mode the
|
|
9
|
+
* resources are still registered (so introspection shows what the server
|
|
10
|
+
* offers) but reads return the NOT_CONNECTED guidance JSON instead of data.
|
|
11
|
+
*/
|
|
12
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
13
|
+
import type { LabelGridClient } from './api/http.js';
|
|
14
|
+
import type { Config } from './config.js';
|
|
15
|
+
/** The reference dataset types as a tuple, for the tool's zod enum. */
|
|
16
|
+
export declare const REFERENCE_TYPES: readonly ["genres", "genre_categories", "languages", "contributor_roles", "instruments", "distro_outlets", "territories", "issue_definitions", "webhook_event_types"];
|
|
17
|
+
export type ReferenceType = (typeof REFERENCE_TYPES)[number];
|
|
18
|
+
export declare const REFERENCE_DATASETS: Record<ReferenceType, {
|
|
19
|
+
path: string;
|
|
20
|
+
title: string;
|
|
21
|
+
description: string;
|
|
22
|
+
}>;
|
|
23
|
+
/** The URI for one reference dataset resource. */
|
|
24
|
+
export declare function referenceUri(type: ReferenceType): string;
|
|
25
|
+
/**
|
|
26
|
+
* Registers the nine `labelgrid://reference/{type}` resources on the server.
|
|
27
|
+
* Reads fetch via the client; in setup mode they return NOT_CONNECTED guidance.
|
|
28
|
+
*/
|
|
29
|
+
export declare function registerReferenceResources(server: Pick<McpServer, 'registerResource'>, config: Config, client: LabelGridClient): void;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP resources: the nine reference datasets, exposed at
|
|
3
|
+
* `labelgrid://reference/{type}`.
|
|
4
|
+
*
|
|
5
|
+
* Each read fetches the dataset via the shared client (no caching) and returns
|
|
6
|
+
* JSON text. The same datasets are served by the `list_reference_data` tool,
|
|
7
|
+
* which is the fallback for clients that don't surface resources — the
|
|
8
|
+
* type→path map lives here as the single source for both. In setup mode the
|
|
9
|
+
* resources are still registered (so introspection shows what the server
|
|
10
|
+
* offers) but reads return the NOT_CONNECTED guidance JSON instead of data.
|
|
11
|
+
*/
|
|
12
|
+
/** The reference dataset types as a tuple, for the tool's zod enum. */
|
|
13
|
+
export const REFERENCE_TYPES = [
|
|
14
|
+
'genres',
|
|
15
|
+
'genre_categories',
|
|
16
|
+
'languages',
|
|
17
|
+
'contributor_roles',
|
|
18
|
+
'instruments',
|
|
19
|
+
'distro_outlets',
|
|
20
|
+
'territories',
|
|
21
|
+
'issue_definitions',
|
|
22
|
+
'webhook_event_types',
|
|
23
|
+
];
|
|
24
|
+
export const REFERENCE_DATASETS = {
|
|
25
|
+
genres: {
|
|
26
|
+
path: '/genres',
|
|
27
|
+
title: 'Genres',
|
|
28
|
+
description: 'Valid values for primary/secondary/tertiary genre IDs on releases.',
|
|
29
|
+
},
|
|
30
|
+
genre_categories: {
|
|
31
|
+
path: '/genre-categories',
|
|
32
|
+
title: 'Genre categories',
|
|
33
|
+
description: 'The genre category groupings the genre IDs belong to.',
|
|
34
|
+
},
|
|
35
|
+
languages: {
|
|
36
|
+
path: '/languages',
|
|
37
|
+
title: 'Languages',
|
|
38
|
+
description: 'Audio and metadata language codes.',
|
|
39
|
+
},
|
|
40
|
+
contributor_roles: {
|
|
41
|
+
path: '/contributor-roles',
|
|
42
|
+
title: 'Contributor roles',
|
|
43
|
+
description: 'Valid role names for track contributors.',
|
|
44
|
+
},
|
|
45
|
+
instruments: {
|
|
46
|
+
path: '/instruments',
|
|
47
|
+
title: 'Instruments',
|
|
48
|
+
description: 'Instrument names for contributor credits.',
|
|
49
|
+
},
|
|
50
|
+
distro_outlets: {
|
|
51
|
+
path: '/distro-outlets',
|
|
52
|
+
title: 'Distribution outlets',
|
|
53
|
+
description: 'The distribution outlets/stores available to your account.',
|
|
54
|
+
},
|
|
55
|
+
territories: {
|
|
56
|
+
path: '/territories',
|
|
57
|
+
title: 'Territories',
|
|
58
|
+
description: 'Country/territory codes.',
|
|
59
|
+
},
|
|
60
|
+
issue_definitions: {
|
|
61
|
+
path: '/issue-definitions',
|
|
62
|
+
title: 'Issue definitions',
|
|
63
|
+
description: 'The catalog of review issue definitions: each code’s human-readable title, description, severity and whether it blocks distribution. Issue codes are string slugs.',
|
|
64
|
+
},
|
|
65
|
+
webhook_event_types: {
|
|
66
|
+
path: '/webhooks/event-types',
|
|
67
|
+
title: 'Webhook event types',
|
|
68
|
+
description: 'Every available webhook event type, each with the schema of the payload it delivers.',
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
/** The URI for one reference dataset resource. */
|
|
72
|
+
export function referenceUri(type) {
|
|
73
|
+
return `labelgrid://reference/${type}`;
|
|
74
|
+
}
|
|
75
|
+
function asJsonText(uri, value) {
|
|
76
|
+
return {
|
|
77
|
+
contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(value, null, 2) }],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Registers the nine `labelgrid://reference/{type}` resources on the server.
|
|
82
|
+
* Reads fetch via the client; in setup mode they return NOT_CONNECTED guidance.
|
|
83
|
+
*/
|
|
84
|
+
export function registerReferenceResources(server, config, client) {
|
|
85
|
+
for (const type of REFERENCE_TYPES) {
|
|
86
|
+
const dataset = REFERENCE_DATASETS[type];
|
|
87
|
+
const uri = referenceUri(type);
|
|
88
|
+
server.registerResource(`reference-${type}`, uri, {
|
|
89
|
+
title: dataset.title,
|
|
90
|
+
description: dataset.description,
|
|
91
|
+
mimeType: 'application/json',
|
|
92
|
+
}, async () => {
|
|
93
|
+
if (config.setupMode) {
|
|
94
|
+
return asJsonText(uri, {
|
|
95
|
+
error: {
|
|
96
|
+
code: 'NOT_CONNECTED',
|
|
97
|
+
message: 'No LabelGrid API token is configured, so this resource cannot be read yet. ' +
|
|
98
|
+
'Call the `setup` tool for step-by-step instructions to connect your account.',
|
|
99
|
+
status: 0,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
const result = await client.get(dataset.path);
|
|
104
|
+
return asJsonText(uri, 'data' in result ? result.data : { error: result.error });
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
package/dist/server.d.ts
CHANGED
|
@@ -10,6 +10,6 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
12
12
|
import type { LabelGridClient } from './api/http.js';
|
|
13
|
-
import type
|
|
13
|
+
import { type Config } from './config.js';
|
|
14
14
|
import { type ToolDef } from './tools/types.js';
|
|
15
15
|
export declare function buildServer(config: Config, client: LabelGridClient, tools: ToolDef[]): McpServer;
|