@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
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Insights toolset: the streaming analytics summary and the consolidated
|
|
3
|
+
* artificial-streaming query (early-warning flags, reported records, and the
|
|
4
|
+
* fee breakdown). All read-only.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { applyProjection } from '../projection.js';
|
|
8
|
+
/** The 15 metric sections the summary endpoint can return. */
|
|
9
|
+
const METRICS = [
|
|
10
|
+
'streams',
|
|
11
|
+
'listeners',
|
|
12
|
+
'saves',
|
|
13
|
+
'skips',
|
|
14
|
+
'shares',
|
|
15
|
+
'completion-rate',
|
|
16
|
+
'lyrics-view-rate',
|
|
17
|
+
'canvas-view-rate',
|
|
18
|
+
'device-split',
|
|
19
|
+
'source-split',
|
|
20
|
+
'saves-by-tier',
|
|
21
|
+
'streams-by-country',
|
|
22
|
+
'streams-by-gender',
|
|
23
|
+
'streams-by-age',
|
|
24
|
+
'shares-by-country',
|
|
25
|
+
];
|
|
26
|
+
const getAnalytics = {
|
|
27
|
+
name: 'get_analytics',
|
|
28
|
+
toolset: 'insights',
|
|
29
|
+
gate: 'read',
|
|
30
|
+
title: 'Get streaming analytics',
|
|
31
|
+
description: 'Retrieve a streaming analytics summary for your catalog in a single call. `start_date` and `end_date` (both YYYY-MM-DD) are required and the window is capped at 30 days by the server. ' +
|
|
32
|
+
'Optionally narrow the result by `platform` (SPOTIFY, ITUNES, APPLE_MUSIC), `release_id`, `isrc`, `upc`, or `artist_names`. ' +
|
|
33
|
+
'By default all 15 metric sections are returned; pass `metrics` (see its enum) to request only a subset. ' +
|
|
34
|
+
'Rate-limited (about 60 requests per minute); a 429 response carries retry_after_seconds.',
|
|
35
|
+
inputShape: {
|
|
36
|
+
start_date: z.string().describe('Start of the reporting window, YYYY-MM-DD.'),
|
|
37
|
+
end_date: z.string().describe('End of the reporting window, YYYY-MM-DD (max 30-day span).'),
|
|
38
|
+
metrics: z
|
|
39
|
+
.array(z.enum(METRICS))
|
|
40
|
+
.optional()
|
|
41
|
+
.describe('Subset of metric sections to return; omit for all 15.'),
|
|
42
|
+
platform: z.enum(['SPOTIFY', 'ITUNES', 'APPLE_MUSIC']).optional(),
|
|
43
|
+
release_id: z.number().int().positive().optional(),
|
|
44
|
+
isrc: z.string().optional(),
|
|
45
|
+
upc: z.string().optional(),
|
|
46
|
+
artist_names: z.array(z.string()).optional().describe('Filter to one or more artist names.'),
|
|
47
|
+
limit: z.number().int().positive().optional(),
|
|
48
|
+
},
|
|
49
|
+
annotations: { readOnlyHint: true },
|
|
50
|
+
handler: (args, { client }) => client.get('/analytics/summary', {
|
|
51
|
+
filter: {
|
|
52
|
+
start_date: args.start_date,
|
|
53
|
+
end_date: args.end_date,
|
|
54
|
+
platform: args.platform,
|
|
55
|
+
release_id: args.release_id,
|
|
56
|
+
isrc: args.isrc,
|
|
57
|
+
upc: args.upc,
|
|
58
|
+
artist_names: args.artist_names,
|
|
59
|
+
},
|
|
60
|
+
metrics: args.metrics,
|
|
61
|
+
limit: args.limit,
|
|
62
|
+
}),
|
|
63
|
+
};
|
|
64
|
+
const queryArtificialStreaming = {
|
|
65
|
+
name: 'query_artificial_streaming',
|
|
66
|
+
toolset: 'insights',
|
|
67
|
+
gate: 'read',
|
|
68
|
+
title: 'Query artificial-streaming data',
|
|
69
|
+
description: 'Query artificial-streaming (streaming-integrity) data for your catalog. Pick ONE view with `view`: ' +
|
|
70
|
+
'`flags` lists Stream Radar early-warning flags surfacing possible artificial-streaming activity so you can act early, paginated — `filters`: status, severity, dsp, isrc, release_id, detected_from/detected_to (YYYY-MM-DD). ' +
|
|
71
|
+
'`flag_detail` retrieves one flag by `flag_id` (required). Stream Radar is an optional add-on; without it the API returns a 403, surfaced verbatim. ' +
|
|
72
|
+
'`records` lists the artificial-streaming records reported for your catalog, cursor-paginated — the per-record detail behind any artificial-streaming fee; `filters`: dsp (spotify or apple), start_date/end_date, release_id, isrc. ' +
|
|
73
|
+
'`fee_breakdown` retrieves the per-release breakdown of an artificial-streaming fee for one billing period — `period` (required) is YYYY-MM. ' +
|
|
74
|
+
"response_format:'detailed' returns the verbatim API response.",
|
|
75
|
+
inputShape: {
|
|
76
|
+
view: z
|
|
77
|
+
.enum(['flags', 'flag_detail', 'records', 'fee_breakdown'])
|
|
78
|
+
.describe('Which artificial-streaming read.'),
|
|
79
|
+
flag_id: z.number().int().positive().optional().describe('Required for view flag_detail.'),
|
|
80
|
+
period: z.string().optional().describe('YYYY-MM. Required for view fee_breakdown.'),
|
|
81
|
+
filters: z
|
|
82
|
+
.record(z.string(), z.unknown())
|
|
83
|
+
.optional()
|
|
84
|
+
.describe('Filter names → values, passed through verbatim.'),
|
|
85
|
+
cursor: z.string().optional().describe('Pagination cursor (view records).'),
|
|
86
|
+
page: z.number().int().positive().optional().describe('1-based page number (view flags).'),
|
|
87
|
+
per_page: z.number().int().positive().optional().describe('Items per page.'),
|
|
88
|
+
response_format: z
|
|
89
|
+
.enum(['concise', 'detailed'])
|
|
90
|
+
.optional()
|
|
91
|
+
.describe("'concise' (default) keeps only the high-signal fields (ids always kept); 'detailed' returns the verbatim API response."),
|
|
92
|
+
},
|
|
93
|
+
annotations: { readOnlyHint: true },
|
|
94
|
+
handler: async (args, { client }) => {
|
|
95
|
+
const view = args.view;
|
|
96
|
+
if (view === 'flag_detail' && args.flag_id === undefined) {
|
|
97
|
+
return {
|
|
98
|
+
error: {
|
|
99
|
+
code: 'INVALID_SELECTOR',
|
|
100
|
+
message: "view 'flag_detail' requires `flag_id` — the flag to retrieve.",
|
|
101
|
+
status: 0,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (view === 'fee_breakdown' && args.period === undefined) {
|
|
106
|
+
return {
|
|
107
|
+
error: {
|
|
108
|
+
code: 'INVALID_SELECTOR',
|
|
109
|
+
message: "view 'fee_breakdown' requires `period` — the billing month, YYYY-MM.",
|
|
110
|
+
status: 0,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
let result;
|
|
115
|
+
if (view === 'flags') {
|
|
116
|
+
result = await client.get('/stream-radar/flags', {
|
|
117
|
+
page: args.page,
|
|
118
|
+
per_page: args.per_page,
|
|
119
|
+
filter: args.filters,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
else if (view === 'flag_detail') {
|
|
123
|
+
result = await client.get(`/stream-radar/flags/${args.flag_id}`);
|
|
124
|
+
}
|
|
125
|
+
else if (view === 'records') {
|
|
126
|
+
// The records endpoint takes its filters as top-level query params.
|
|
127
|
+
result = await client.get('/royalties/artificial-streams', {
|
|
128
|
+
...(args.filters ?? {}),
|
|
129
|
+
cursor: args.cursor,
|
|
130
|
+
per_page: args.per_page,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
result = await client.get(`/artificial-streaming-fee/${encodeURIComponent(String(args.period))}`);
|
|
135
|
+
}
|
|
136
|
+
return applyProjection(result, 'query_artificial_streaming', args.response_format);
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
export const insightsTools = [getAnalytics, queryArtificialStreaming];
|
package/dist/tools/reference.js
CHANGED
|
@@ -1,36 +1,21 @@
|
|
|
1
1
|
/** Reference toolset: one tool serving all read-only lookup datasets. */
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
|
|
4
|
-
const REFERENCE_PATHS = {
|
|
5
|
-
genres: '/genres',
|
|
6
|
-
genre_categories: '/genre-categories',
|
|
7
|
-
languages: '/languages',
|
|
8
|
-
contributor_roles: '/contributor-roles',
|
|
9
|
-
instruments: '/instruments',
|
|
10
|
-
distro_outlets: '/distro-outlets',
|
|
11
|
-
territories: '/territories',
|
|
12
|
-
};
|
|
3
|
+
import { REFERENCE_DATASETS, REFERENCE_TYPES } from '../resources.js';
|
|
13
4
|
const listReferenceData = {
|
|
14
5
|
name: 'list_reference_data',
|
|
15
6
|
toolset: 'reference',
|
|
16
7
|
gate: 'read',
|
|
17
8
|
title: 'List reference data',
|
|
18
|
-
description: 'Fetch a LabelGrid reference dataset used to resolve the IDs and codes
|
|
19
|
-
'`genres` and `genre_categories` (
|
|
20
|
-
'`
|
|
21
|
-
'or `
|
|
9
|
+
description: 'Fetch a LabelGrid reference dataset used to resolve the IDs and codes the catalog and release tools expect. Pick ONE dataset with `type`: ' +
|
|
10
|
+
'`genres` and `genre_categories` (genre IDs), `languages` (audio/metadata language codes), `contributor_roles`, `instruments`, `distro_outlets` (the outlets/stores available to your account), ' +
|
|
11
|
+
'`territories` (country codes), `issue_definitions` (each review issue code’s title, description, severity and whether it blocks distribution; codes are string slugs), ' +
|
|
12
|
+
'or `webhook_event_types` (every webhook event type with its payload schema). ' +
|
|
13
|
+
'Call this when you need a valid ID or code. ' +
|
|
14
|
+
'The same datasets are exposed as MCP resources at labelgrid://reference/{type}; this tool is the fallback for clients that don’t surface resources.',
|
|
22
15
|
inputShape: {
|
|
23
|
-
type: z.enum(
|
|
24
|
-
'genres',
|
|
25
|
-
'genre_categories',
|
|
26
|
-
'languages',
|
|
27
|
-
'contributor_roles',
|
|
28
|
-
'instruments',
|
|
29
|
-
'distro_outlets',
|
|
30
|
-
'territories',
|
|
31
|
-
]),
|
|
16
|
+
type: z.enum(REFERENCE_TYPES),
|
|
32
17
|
},
|
|
33
18
|
annotations: { readOnlyHint: true },
|
|
34
|
-
handler: (args, { client }) => client.get(
|
|
19
|
+
handler: (args, { client }) => client.get(REFERENCE_DATASETS[args.type].path),
|
|
35
20
|
};
|
|
36
21
|
export const referenceTools = [listReferenceData];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Releases toolset: release-level reads (review results, the delivery queue,
|
|
3
|
+
* the smart-link landing config, track licenses) plus the safe-write release
|
|
4
|
+
* checks, landing-page management and review-issue notes.
|
|
5
|
+
*
|
|
6
|
+
* The two [proj] reads (`get_release_review`, `get_delivery_queue`) default to
|
|
7
|
+
* concise-mode projection; `response_format: 'detailed'` returns the verbatim
|
|
8
|
+
* API response.
|
|
9
|
+
*/
|
|
10
|
+
import type { ToolDef } from './types.js';
|
|
11
|
+
export declare const releaseTools: ToolDef[];
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Releases toolset: release-level reads (review results, the delivery queue,
|
|
3
|
+
* the smart-link landing config, track licenses) plus the safe-write release
|
|
4
|
+
* checks, landing-page management and review-issue notes.
|
|
5
|
+
*
|
|
6
|
+
* The two [proj] reads (`get_release_review`, `get_delivery_queue`) default to
|
|
7
|
+
* concise-mode projection; `response_format: 'detailed'` returns the verbatim
|
|
8
|
+
* API response.
|
|
9
|
+
*/
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { applyProjection } from '../projection.js';
|
|
12
|
+
const releaseId = z.number().int().positive().describe('The release id.');
|
|
13
|
+
const responseFormat = z
|
|
14
|
+
.enum(['concise', 'detailed'])
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("'concise' (default) keeps only the high-signal fields (ids always kept); 'detailed' returns the verbatim API response.");
|
|
17
|
+
const getReleaseReview = {
|
|
18
|
+
name: 'get_release_review',
|
|
19
|
+
toolset: 'releases',
|
|
20
|
+
gate: 'read',
|
|
21
|
+
title: 'Get release review results',
|
|
22
|
+
description: "Read a release's automated quality-check results. Pick ONE view with `view`: " +
|
|
23
|
+
'`issues` lists the review issues raised against the release — each carries a code (see list_reference_data type issue_definitions), severity, and whether it blocks distribution; use it to see what must be fixed before the release can go out. ' +
|
|
24
|
+
'`quality_report` retrieves the Preflight QC quality report — the customer-facing issues found by the automated checks, to review before confirming the release into distribution; Preflight QC is an optional add-on — without it the API returns a 403, surfaced verbatim. ' +
|
|
25
|
+
"response_format:'detailed' returns the verbatim API response.",
|
|
26
|
+
inputShape: {
|
|
27
|
+
release_id: releaseId,
|
|
28
|
+
view: z.enum(['issues', 'quality_report']).describe('Which review read.'),
|
|
29
|
+
response_format: responseFormat,
|
|
30
|
+
},
|
|
31
|
+
annotations: { readOnlyHint: true },
|
|
32
|
+
handler: async (args, { client }) => {
|
|
33
|
+
const result = args.view === 'quality_report'
|
|
34
|
+
? await client.get(`/releases/${args.release_id}/quality-report`)
|
|
35
|
+
: await client.get('/review-issues', { release_id: args.release_id });
|
|
36
|
+
return applyProjection(result, 'get_release_review', args.response_format);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
const getDeliveryQueue = {
|
|
40
|
+
name: 'get_delivery_queue',
|
|
41
|
+
toolset: 'releases',
|
|
42
|
+
gate: 'read',
|
|
43
|
+
title: 'Get the distribution queue',
|
|
44
|
+
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) — where a release is in the delivery pipeline to each store. Filter by `release_id`, `outlet_id`, or `status`. ' +
|
|
45
|
+
"response_format:'detailed' returns the verbatim API response.",
|
|
46
|
+
inputShape: {
|
|
47
|
+
release_id: z.number().int().positive().optional().describe('Filter to one release.'),
|
|
48
|
+
outlet_id: z.number().int().positive().optional().describe('Filter to one outlet/store.'),
|
|
49
|
+
status: z.string().optional().describe('Filter by delivery status.'),
|
|
50
|
+
page: z.number().int().positive().optional(),
|
|
51
|
+
per_page: z.number().int().positive().optional(),
|
|
52
|
+
response_format: responseFormat,
|
|
53
|
+
},
|
|
54
|
+
annotations: { readOnlyHint: true },
|
|
55
|
+
handler: async (args, { client }) => {
|
|
56
|
+
const result = await client.get('/queues/distro', {
|
|
57
|
+
page: args.page,
|
|
58
|
+
per_page: args.per_page,
|
|
59
|
+
filter: {
|
|
60
|
+
release_id: args.release_id,
|
|
61
|
+
outlet_id: args.outlet_id,
|
|
62
|
+
status: args.status,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
return applyProjection(result, 'get_delivery_queue', args.response_format);
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
const getLandingConfig = {
|
|
69
|
+
name: 'get_landing_config',
|
|
70
|
+
toolset: 'releases',
|
|
71
|
+
gate: 'read',
|
|
72
|
+
title: 'Get a release landing-page config',
|
|
73
|
+
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 manage_release_links (action update_landing_config) to change it.',
|
|
74
|
+
inputShape: { release_id: releaseId },
|
|
75
|
+
annotations: { readOnlyHint: true },
|
|
76
|
+
handler: (args, { client }) => client.get(`/releases/${args.release_id}/landing-config`),
|
|
77
|
+
};
|
|
78
|
+
const listTrackLicenses = {
|
|
79
|
+
name: 'list_track_licenses',
|
|
80
|
+
toolset: 'releases',
|
|
81
|
+
gate: 'read',
|
|
82
|
+
title: 'List track licenses',
|
|
83
|
+
description: 'List the licenses attached to a track (e.g. cover/mechanical or sample clearances), paginated. Pass `license_id` to retrieve one license by its id instead.',
|
|
84
|
+
inputShape: {
|
|
85
|
+
track_id: z.number().int().positive().describe('The track id.'),
|
|
86
|
+
license_id: z
|
|
87
|
+
.number()
|
|
88
|
+
.int()
|
|
89
|
+
.positive()
|
|
90
|
+
.optional()
|
|
91
|
+
.describe('Retrieve one license by id instead of listing.'),
|
|
92
|
+
page: z.number().int().positive().optional(),
|
|
93
|
+
per_page: z.number().int().positive().optional(),
|
|
94
|
+
},
|
|
95
|
+
annotations: { readOnlyHint: true },
|
|
96
|
+
handler: (args, { client }) => {
|
|
97
|
+
if (args.license_id !== undefined) {
|
|
98
|
+
return client.get(`/tracks/${args.track_id}/licenses/${args.license_id}`);
|
|
99
|
+
}
|
|
100
|
+
return client.get(`/tracks/${args.track_id}/licenses`, {
|
|
101
|
+
page: args.page,
|
|
102
|
+
per_page: args.per_page,
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
const runReleaseChecks = {
|
|
107
|
+
name: 'run_release_checks',
|
|
108
|
+
toolset: 'releases',
|
|
109
|
+
gate: 'safe_write',
|
|
110
|
+
title: 'Run release checks',
|
|
111
|
+
description: 'Run an automated check on a release. Pick ONE with `check`: ' +
|
|
112
|
+
'`validate` returns any problems that would block distribution, as a human-readable `errors` list and a machine-readable `errors_structured` list — it changes nothing and is safe to repeat; run it before distributing. ' +
|
|
113
|
+
'`refresh_quality_report` re-runs the Preflight QC checks and refreshes the quality report (read it with get_release_review view quality_report); the server applies an hourly refresh budget, so frequent calls may be rate-limited. Preflight QC is an optional add-on.',
|
|
114
|
+
inputShape: {
|
|
115
|
+
release_id: releaseId,
|
|
116
|
+
check: z.enum(['validate', 'refresh_quality_report']).describe('Which check to run.'),
|
|
117
|
+
},
|
|
118
|
+
annotations: { idempotentHint: true },
|
|
119
|
+
handler: (args, { client }) => args.check === 'refresh_quality_report'
|
|
120
|
+
? client.post(`/releases/${args.release_id}/quality-report/refresh`)
|
|
121
|
+
: client.post(`/releases/${args.release_id}/validate`),
|
|
122
|
+
};
|
|
123
|
+
const manageReleaseLinks = {
|
|
124
|
+
name: 'manage_release_links',
|
|
125
|
+
toolset: 'releases',
|
|
126
|
+
gate: 'safe_write',
|
|
127
|
+
title: 'Manage a release smart link',
|
|
128
|
+
description: "Manage a release's smart-link landing page. Pick ONE action with `action`: " +
|
|
129
|
+
'`update_landing_config` replaces the landing-page configuration with `config` (required for this action) — `config.actions` uses the current (v2) action-list contract (one entry per call-to-action); other keys: links_page_enabled, config_mode, page_style, custom_cta_text, custom_description, pre_order_links. ' +
|
|
130
|
+
"`create_short_url` creates (or returns the existing) short URL for the release's smart-link landing page — safe to repeat.",
|
|
131
|
+
inputShape: {
|
|
132
|
+
release_id: releaseId,
|
|
133
|
+
action: z.enum(['update_landing_config', 'create_short_url']).describe('Which action.'),
|
|
134
|
+
config: z
|
|
135
|
+
.record(z.string(), z.unknown())
|
|
136
|
+
.optional()
|
|
137
|
+
.describe('The landing-page configuration to set (required for update_landing_config).'),
|
|
138
|
+
},
|
|
139
|
+
annotations: {},
|
|
140
|
+
handler: (args, { client }) => {
|
|
141
|
+
if (args.action === 'create_short_url') {
|
|
142
|
+
return client.post('/releases/short-url', { release_id: args.release_id });
|
|
143
|
+
}
|
|
144
|
+
if (args.config === undefined) {
|
|
145
|
+
return Promise.resolve({
|
|
146
|
+
error: {
|
|
147
|
+
code: 'INVALID_SELECTOR',
|
|
148
|
+
message: "action 'update_landing_config' requires `config` — the landing-page configuration to set.",
|
|
149
|
+
status: 0,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return client.put(`/releases/${args.release_id}/landing-config`, args.config);
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
const addReviewIssueNote = {
|
|
157
|
+
name: 'add_review_issue_note',
|
|
158
|
+
toolset: 'releases',
|
|
159
|
+
gate: 'safe_write',
|
|
160
|
+
title: 'Add a note to a review issue',
|
|
161
|
+
description: 'Add a note to a release review issue — to explain a fix or add reviewer context. `review_issue_id` comes from get_release_review view issues.',
|
|
162
|
+
inputShape: {
|
|
163
|
+
review_issue_id: z.number().int().positive(),
|
|
164
|
+
note: z.string().describe('The note text to attach to the issue.'),
|
|
165
|
+
},
|
|
166
|
+
annotations: {},
|
|
167
|
+
handler: (args, { client }) => client.post(`/review-issues/${args.review_issue_id}/notes`, { note: args.note }),
|
|
168
|
+
};
|
|
169
|
+
export const releaseTools = [
|
|
170
|
+
getReleaseReview,
|
|
171
|
+
getDeliveryQueue,
|
|
172
|
+
getLandingConfig,
|
|
173
|
+
listTrackLicenses,
|
|
174
|
+
runReleaseChecks,
|
|
175
|
+
manageReleaseLinks,
|
|
176
|
+
addReviewIssueNote,
|
|
177
|
+
];
|
package/dist/tools/setup.js
CHANGED
|
@@ -39,7 +39,7 @@ const SETUP_GUIDE = {
|
|
|
39
39
|
optional_settings: [
|
|
40
40
|
'LABELGRID_ENABLE_WRITES — safe draft-stage writes; on by default, set false for read-only (see the README Safety section).',
|
|
41
41
|
'LABELGRID_ENABLE_FULL_WRITES (plus LABELGRID_FULL_WRITES_ACK) — arm consequential distribution actions; off by default (see the README Safety section).',
|
|
42
|
-
'LABELGRID_TOOLSETS — expose only a comma-separated subset of toolsets (see the README
|
|
42
|
+
'LABELGRID_TOOLSETS — expose only a comma-separated subset of toolsets: account, reference, catalog, releases, insights, finance, webhooks, distribution (pre-0.3.0 names are still accepted as aliases). The webhooks toolset is off by default — name it explicitly here to enable it (see the README Configuration section).',
|
|
43
43
|
'LABELGRID_READ_ONLY — force reads only, overriding the write flags (see the README Safety section).',
|
|
44
44
|
],
|
|
45
45
|
};
|
package/dist/tools/webhooks.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Webhooks toolset: read your webhook subscriptions and delivery logs
|
|
3
|
-
* manage them (create/update/delete/test/
|
|
4
|
-
* the mutations are safe writes.
|
|
2
|
+
* Webhooks toolset: read your webhook subscriptions and delivery logs
|
|
3
|
+
* (list_webhooks) and manage them (manage_webhook: create/update/delete/test/
|
|
4
|
+
* rotate_secret). The read is always on; the mutations are safe writes.
|
|
5
5
|
*/
|
|
6
6
|
import type { ToolDef } from './types.js';
|
|
7
7
|
export declare const webhookTools: ToolDef[];
|
package/dist/tools/webhooks.js
CHANGED
|
@@ -1,124 +1,92 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Webhooks toolset: read your webhook subscriptions and delivery logs
|
|
3
|
-
* manage them (create/update/delete/test/
|
|
4
|
-
* the mutations are safe writes.
|
|
2
|
+
* Webhooks toolset: read your webhook subscriptions and delivery logs
|
|
3
|
+
* (list_webhooks) and manage them (manage_webhook: create/update/delete/test/
|
|
4
|
+
* rotate_secret). The read is always on; the mutations are safe writes.
|
|
5
5
|
*/
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
-
const webhookId = z.number().int().positive().describe('The webhook id.');
|
|
8
|
-
const eventsShape = z
|
|
9
|
-
.record(z.string(), z.unknown())
|
|
10
|
-
.describe('The event subscription object selecting which event types this webhook receives. Call list_webhook_event_types for the available types and each payload shape.');
|
|
7
|
+
const webhookId = z.number().int().positive().optional().describe('The webhook id.');
|
|
11
8
|
const listWebhooks = {
|
|
12
9
|
name: 'list_webhooks',
|
|
13
10
|
toolset: 'webhooks',
|
|
14
11
|
gate: 'read',
|
|
15
12
|
title: 'List webhooks',
|
|
16
|
-
description: '
|
|
17
|
-
|
|
18
|
-
annotations: { readOnlyHint: true },
|
|
19
|
-
handler: (_args, { client }) => client.get('/webhooks'),
|
|
20
|
-
};
|
|
21
|
-
const getWebhook = {
|
|
22
|
-
name: 'get_webhook',
|
|
23
|
-
toolset: 'webhooks',
|
|
24
|
-
gate: 'read',
|
|
25
|
-
title: 'Get a webhook',
|
|
26
|
-
description: 'Retrieve one webhook subscription by id.',
|
|
27
|
-
inputShape: { webhook_id: webhookId },
|
|
28
|
-
annotations: { readOnlyHint: true },
|
|
29
|
-
handler: (args, { client }) => client.get(`/webhooks/${args.webhook_id}`),
|
|
30
|
-
};
|
|
31
|
-
const getWebhookLogs = {
|
|
32
|
-
name: 'get_webhook_logs',
|
|
33
|
-
toolset: 'webhooks',
|
|
34
|
-
gate: 'read',
|
|
35
|
-
title: 'Get webhook delivery logs',
|
|
36
|
-
description: 'Retrieve the recent delivery log for a webhook — the attempts, response codes and outcomes — to debug why events did or did not reach your endpoint.',
|
|
37
|
-
inputShape: { webhook_id: webhookId },
|
|
38
|
-
annotations: { readOnlyHint: true },
|
|
39
|
-
handler: (args, { client }) => client.get(`/webhooks/${args.webhook_id}/logs`),
|
|
40
|
-
};
|
|
41
|
-
const listWebhookEventTypes = {
|
|
42
|
-
name: 'list_webhook_event_types',
|
|
43
|
-
toolset: 'webhooks',
|
|
44
|
-
gate: 'read',
|
|
45
|
-
title: 'List webhook event types',
|
|
46
|
-
description: 'List every available webhook event type, each with the schema of the payload it delivers. Use it to decide which events to subscribe a webhook to.',
|
|
47
|
-
inputShape: {},
|
|
48
|
-
annotations: { readOnlyHint: true },
|
|
49
|
-
handler: (_args, { client }) => client.get('/webhooks/event-types'),
|
|
50
|
-
};
|
|
51
|
-
const createWebhook = {
|
|
52
|
-
name: 'create_webhook',
|
|
53
|
-
toolset: 'webhooks',
|
|
54
|
-
gate: 'safe_write',
|
|
55
|
-
title: 'Create a webhook',
|
|
56
|
-
description: 'Create a webhook subscription. `name` and `url` (the HTTPS endpoint that will receive events) are required, along with `events` selecting which event types to deliver. The API returns a signing secret once on creation — store it to verify incoming payloads.',
|
|
13
|
+
description: "Read your webhook subscriptions. `view: 'config'` (the default) lists the webhook subscriptions configured on your account — each with its URL, subscribed events and active state — or retrieves one subscription when `webhook_id` is given. " +
|
|
14
|
+
"`view: 'logs'` retrieves the recent delivery log for a webhook (`webhook_id` required) — attempts, response codes and outcomes — to debug why events did or did not reach your endpoint.",
|
|
57
15
|
inputShape: {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
16
|
+
webhook_id: webhookId,
|
|
17
|
+
view: z
|
|
18
|
+
.enum(['config', 'logs'])
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('config (default) reads subscriptions; logs reads a webhook’s delivery log.'),
|
|
21
|
+
},
|
|
22
|
+
annotations: { readOnlyHint: true },
|
|
23
|
+
handler: (args, { client }) => {
|
|
24
|
+
const view = args.view ?? 'config';
|
|
25
|
+
if (view === 'logs') {
|
|
26
|
+
if (args.webhook_id === undefined) {
|
|
27
|
+
return Promise.resolve({
|
|
28
|
+
error: {
|
|
29
|
+
code: 'INVALID_SELECTOR',
|
|
30
|
+
message: "view 'logs' requires `webhook_id` — the webhook whose delivery log to read.",
|
|
31
|
+
status: 0,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return client.get(`/webhooks/${args.webhook_id}/logs`);
|
|
36
|
+
}
|
|
37
|
+
if (args.webhook_id !== undefined) {
|
|
38
|
+
return client.get(`/webhooks/${args.webhook_id}`);
|
|
39
|
+
}
|
|
40
|
+
return client.get('/webhooks');
|
|
61
41
|
},
|
|
62
|
-
annotations: {},
|
|
63
|
-
handler: (args, { client }) => client.post('/webhooks', { name: args.name, url: args.url, events: args.events }),
|
|
64
42
|
};
|
|
65
|
-
const
|
|
66
|
-
name: '
|
|
43
|
+
const manageWebhook = {
|
|
44
|
+
name: 'manage_webhook',
|
|
67
45
|
toolset: 'webhooks',
|
|
68
46
|
gate: 'safe_write',
|
|
69
|
-
title: '
|
|
70
|
-
description: '
|
|
47
|
+
title: 'Manage a webhook',
|
|
48
|
+
description: 'Manage a webhook subscription. Pick ONE action with `action`: ' +
|
|
49
|
+
'`create` — pass `fields` with `name`, `url` (the HTTPS endpoint receiving deliveries) and `events` (the event subscription object — see list_reference_data type webhook_event_types); the API returns a signing secret ONCE on creation — store it to verify incoming payloads. ' +
|
|
50
|
+
'`update` — supply only the fields to change in `fields`: name, url, events, or is_active (false pauses deliveries). ' +
|
|
51
|
+
'`delete` — permanently removes the subscription; it stops receiving events. ' +
|
|
52
|
+
'`test` — sends a test event to confirm reachability and signature verification; safe to repeat. ' +
|
|
53
|
+
'`rotate_secret` — generates and returns a new signing secret — WARNING: the old secret stops working immediately; update your endpoint right away or deliveries will fail verification. ' +
|
|
54
|
+
'`webhook_id` is required for every action except create.',
|
|
71
55
|
inputShape: {
|
|
56
|
+
action: z
|
|
57
|
+
.enum(['create', 'update', 'delete', 'test', 'rotate_secret'])
|
|
58
|
+
.describe('Which action.'),
|
|
72
59
|
webhook_id: webhookId,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
60
|
+
fields: z
|
|
61
|
+
.record(z.string(), z.unknown())
|
|
62
|
+
.optional()
|
|
63
|
+
.describe('The webhook attributes (create: name, url, events; update: any of those plus is_active), forwarded verbatim.'),
|
|
77
64
|
},
|
|
78
|
-
annotations: {},
|
|
65
|
+
annotations: { destructiveHint: true },
|
|
79
66
|
handler: (args, { client }) => {
|
|
80
|
-
const
|
|
81
|
-
|
|
67
|
+
const action = args.action;
|
|
68
|
+
if (action === 'create') {
|
|
69
|
+
return client.post('/webhooks', args.fields);
|
|
70
|
+
}
|
|
71
|
+
if (args.webhook_id === undefined) {
|
|
72
|
+
return Promise.resolve({
|
|
73
|
+
error: {
|
|
74
|
+
code: 'INVALID_SELECTOR',
|
|
75
|
+
message: `action '${action}' requires \`webhook_id\` — the webhook to act on. Only action 'create' works without one.`,
|
|
76
|
+
status: 0,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
switch (action) {
|
|
81
|
+
case 'update':
|
|
82
|
+
return client.patch(`/webhooks/${args.webhook_id}`, args.fields);
|
|
83
|
+
case 'delete':
|
|
84
|
+
return client.delete(`/webhooks/${args.webhook_id}`);
|
|
85
|
+
case 'test':
|
|
86
|
+
return client.post(`/webhooks/${args.webhook_id}/test`);
|
|
87
|
+
default: // rotate_secret
|
|
88
|
+
return client.post(`/webhooks/${args.webhook_id}/regenerate-secret`);
|
|
89
|
+
}
|
|
82
90
|
},
|
|
83
91
|
};
|
|
84
|
-
const
|
|
85
|
-
name: 'delete_webhook',
|
|
86
|
-
toolset: 'webhooks',
|
|
87
|
-
gate: 'safe_write',
|
|
88
|
-
title: 'Delete a webhook',
|
|
89
|
-
description: 'Delete a webhook subscription permanently. It will stop receiving events.',
|
|
90
|
-
inputShape: { webhook_id: webhookId },
|
|
91
|
-
annotations: { destructiveHint: true },
|
|
92
|
-
handler: (args, { client }) => client.delete(`/webhooks/${args.webhook_id}`),
|
|
93
|
-
};
|
|
94
|
-
const testWebhook = {
|
|
95
|
-
name: 'test_webhook',
|
|
96
|
-
toolset: 'webhooks',
|
|
97
|
-
gate: 'safe_write',
|
|
98
|
-
title: 'Send a test webhook event',
|
|
99
|
-
description: 'Send a test event to a webhook’s endpoint so you can confirm it is reachable and your signature verification works. Safe to repeat.',
|
|
100
|
-
inputShape: { webhook_id: webhookId },
|
|
101
|
-
annotations: { idempotentHint: true },
|
|
102
|
-
handler: (args, { client }) => client.post(`/webhooks/${args.webhook_id}/test`),
|
|
103
|
-
};
|
|
104
|
-
const rotateWebhookSecret = {
|
|
105
|
-
name: 'rotate_webhook_secret',
|
|
106
|
-
toolset: 'webhooks',
|
|
107
|
-
gate: 'safe_write',
|
|
108
|
-
title: 'Rotate a webhook signing secret',
|
|
109
|
-
description: 'Generate a new signing secret for a webhook and return it. WARNING: the old secret stops working immediately — update your endpoint’s signature verification with the new secret right away or deliveries will fail verification.',
|
|
110
|
-
inputShape: { webhook_id: webhookId },
|
|
111
|
-
annotations: { destructiveHint: true },
|
|
112
|
-
handler: (args, { client }) => client.post(`/webhooks/${args.webhook_id}/regenerate-secret`),
|
|
113
|
-
};
|
|
114
|
-
export const webhookTools = [
|
|
115
|
-
listWebhooks,
|
|
116
|
-
getWebhook,
|
|
117
|
-
getWebhookLogs,
|
|
118
|
-
listWebhookEventTypes,
|
|
119
|
-
createWebhook,
|
|
120
|
-
updateWebhook,
|
|
121
|
-
deleteWebhook,
|
|
122
|
-
testWebhook,
|
|
123
|
-
rotateWebhookSecret,
|
|
124
|
-
];
|
|
92
|
+
export const webhookTools = [listWebhooks, manageWebhook];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@labelgrid/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"mcpName": "io.github.labelgrid/labelgrid-mcp",
|
|
5
5
|
"description": "Official LabelGrid MCP server — connect your AI client to your LabelGrid account",
|
|
6
6
|
"type": "module",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"lint": "biome check .",
|
|
21
21
|
"leak-guard": "node scripts/leak-guard.mjs",
|
|
22
22
|
"check-coverage": "node scripts/check-api-coverage.mjs",
|
|
23
|
-
"gen-docs": "node scripts/gen-tool-docs.mjs"
|
|
23
|
+
"gen-docs": "node scripts/gen-tool-docs.mjs",
|
|
24
|
+
"measure-tokens": "node scripts/measure-tool-tokens.mjs"
|
|
24
25
|
},
|
|
25
26
|
"repository": {
|
|
26
27
|
"type": "git",
|
package/server.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.labelgrid/labelgrid-mcp",
|
|
4
4
|
"description": "Official LabelGrid MCP server — manage your music catalog, releases, analytics and distribution.",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.3.0",
|
|
6
6
|
"websiteUrl": "https://labelgrid.com",
|
|
7
7
|
"repository": {
|
|
8
8
|
"url": "https://github.com/labelgrid/labelgrid-mcp",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "@labelgrid/mcp",
|
|
15
|
-
"version": "0.
|
|
15
|
+
"version": "0.3.0",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
"name": "LABELGRID_TOOLSETS",
|
|
53
|
-
"description": "Comma-separated subset of toolsets to expose (
|
|
53
|
+
"description": "Comma-separated subset of toolsets to expose (account, reference, catalog, releases, insights, finance, webhooks, distribution; pre-0.3.0 names are accepted as aliases). Defaults to all except webhooks, which must be named explicitly to enable.",
|
|
54
54
|
"isRequired": false,
|
|
55
55
|
"format": "string"
|
|
56
56
|
},
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Accounting toolset: statements, transactions, royalty breakdowns,
|
|
3
|
-
* artificial-streaming records, and the account summary. All read-only.
|
|
4
|
-
*
|
|
5
|
-
* Two tools (`download_statement_csv`, `download_statement_invoice`) fetch a
|
|
6
|
-
* file body. They validate the caller-supplied `save_to_path` and write ONLY
|
|
7
|
-
* there; a CSV without a save path is returned inline, truncated at 100KB.
|
|
8
|
-
* These downloads use an authenticated raw GET (the shared client's JSON path
|
|
9
|
-
* would corrupt binary PDFs), with the same auth headers the client sends.
|
|
10
|
-
*/
|
|
11
|
-
import type { ToolDef } from './types.js';
|
|
12
|
-
export declare const accountingTools: ToolDef[];
|