@labelgrid/mcp 0.1.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 +35 -0
- package/LICENSE +21 -0
- package/README.md +299 -0
- package/dist/api/content-types.d.ts +33 -0
- package/dist/api/content-types.js +87 -0
- package/dist/api/http.d.ts +62 -0
- package/dist/api/http.js +345 -0
- package/dist/api/upload.d.ts +26 -0
- package/dist/api/upload.js +104 -0
- package/dist/config.d.ts +29 -0
- package/dist/config.js +82 -0
- package/dist/coverage.d.ts +19 -0
- package/dist/coverage.js +150 -0
- package/dist/gating.d.ts +13 -0
- package/dist/gating.js +22 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +86 -0
- package/dist/legal.d.ts +12 -0
- package/dist/legal.js +13 -0
- package/dist/log.d.ts +16 -0
- package/dist/log.js +35 -0
- package/dist/server.d.ts +15 -0
- package/dist/server.js +83 -0
- package/dist/tools/accounting.d.ts +12 -0
- package/dist/tools/accounting.js +386 -0
- package/dist/tools/analytics.d.ts +3 -0
- package/dist/tools/analytics.js +62 -0
- package/dist/tools/catalog-read.d.ts +10 -0
- package/dist/tools/catalog-read.js +145 -0
- package/dist/tools/catalog-write.d.ts +12 -0
- package/dist/tools/catalog-write.js +206 -0
- package/dist/tools/delivery.d.ts +6 -0
- package/dist/tools/delivery.js +40 -0
- package/dist/tools/files-read.d.ts +7 -0
- package/dist/tools/files-read.js +86 -0
- package/dist/tools/full-writes.d.ts +12 -0
- package/dist/tools/full-writes.js +248 -0
- package/dist/tools/identity.d.ts +3 -0
- package/dist/tools/identity.js +28 -0
- package/dist/tools/reference.d.ts +3 -0
- package/dist/tools/reference.js +36 -0
- package/dist/tools/release-write.d.ts +12 -0
- package/dist/tools/release-write.js +184 -0
- package/dist/tools/review-read.d.ts +7 -0
- package/dist/tools/review-read.js +78 -0
- package/dist/tools/setup.d.ts +11 -0
- package/dist/tools/setup.js +56 -0
- package/dist/tools/types.d.ts +41 -0
- package/dist/tools/types.js +52 -0
- package/dist/tools/webhooks.d.ts +7 -0
- package/dist/tools/webhooks.js +124 -0
- package/dist/version.d.ts +6 -0
- package/dist/version.js +17 -0
- package/package.json +34 -0
- package/server.json +59 -0
package/dist/coverage.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The endpoint-coverage manifest, consumed by the API-coverage drift check.
|
|
3
|
+
*
|
|
4
|
+
* `COVERAGE` maps every public endpoint (method + path) this server exposes as a
|
|
5
|
+
* tool to that tool's name. `EXCLUDED` lists public endpoints deliberately not
|
|
6
|
+
* exposed in v1, each with a short customer-appropriate reason. `PENDING_DOCS`
|
|
7
|
+
* lists tool endpoints whose reference documentation is still being generated
|
|
8
|
+
* (the drift check tolerates their absence from the API document snapshot).
|
|
9
|
+
*
|
|
10
|
+
* The drift check fails when the live API document contains a path+method that
|
|
11
|
+
* is neither covered nor excluded — the signal to add a tool (or an exclusion)
|
|
12
|
+
* in the same cycle the API grows.
|
|
13
|
+
*
|
|
14
|
+
* Keys use the API document's exact path templates (e.g. `{release}`) and an
|
|
15
|
+
* uppercase method followed by a single space.
|
|
16
|
+
*/
|
|
17
|
+
export const COVERAGE = {
|
|
18
|
+
// identity
|
|
19
|
+
'GET /me': 'get_me',
|
|
20
|
+
'DELETE /tokens/current': 'revoke_api_token',
|
|
21
|
+
'DELETE /tokens/{tokenId}': 'revoke_api_token',
|
|
22
|
+
// reference
|
|
23
|
+
'GET /genres': 'list_reference_data',
|
|
24
|
+
'GET /genre-categories': 'list_reference_data',
|
|
25
|
+
'GET /languages': 'list_reference_data',
|
|
26
|
+
'GET /contributor-roles': 'list_reference_data',
|
|
27
|
+
'GET /instruments': 'list_reference_data',
|
|
28
|
+
'GET /distro-outlets': 'list_reference_data',
|
|
29
|
+
'GET /territories': 'list_reference_data',
|
|
30
|
+
// analytics
|
|
31
|
+
'GET /analytics/summary': 'get_analytics',
|
|
32
|
+
// catalog reads
|
|
33
|
+
'GET /labels': 'list_labels',
|
|
34
|
+
'GET /labels/{label}': 'get_label',
|
|
35
|
+
'GET /artists': 'list_artists',
|
|
36
|
+
'GET /artists/{artist}': 'get_artist',
|
|
37
|
+
'GET /writers': 'list_writers',
|
|
38
|
+
'GET /writers/{writer}': 'get_writer',
|
|
39
|
+
'GET /publishers': 'list_publishers',
|
|
40
|
+
'GET /publishers/{publisher}': 'get_publisher',
|
|
41
|
+
'GET /releases': 'list_releases',
|
|
42
|
+
'GET /releases/{release}': 'get_release',
|
|
43
|
+
'GET /tracks': 'list_tracks',
|
|
44
|
+
'GET /tracks/{track}': 'get_track',
|
|
45
|
+
// files reads
|
|
46
|
+
'GET /tracks/{track}/files/{fileType}': 'get_track_file',
|
|
47
|
+
'GET /tracks/{track}/licenses': 'list_track_licenses',
|
|
48
|
+
'GET /tracks/{track}/licenses/{trackLicense}': 'get_track_license',
|
|
49
|
+
'GET /releases/{release}/files/{assetType}': 'get_release_file',
|
|
50
|
+
// review reads
|
|
51
|
+
'GET /review-issues': 'list_review_issues',
|
|
52
|
+
'GET /issue-definitions': 'list_issue_definitions',
|
|
53
|
+
'GET /releases/{release}/quality-report': 'get_quality_report',
|
|
54
|
+
'GET /stream-radar/flags': 'list_stream_radar_flags',
|
|
55
|
+
'GET /stream-radar/flags/{streamRadarFlag}': 'get_stream_radar_flag',
|
|
56
|
+
// delivery
|
|
57
|
+
'GET /queues/distro': 'get_delivery_queue',
|
|
58
|
+
'GET /releases/{release}/landing-config': 'get_landing_config',
|
|
59
|
+
// accounting
|
|
60
|
+
'GET /statements': 'list_statements',
|
|
61
|
+
'GET /statements/{invoiceNumber}': 'get_statement',
|
|
62
|
+
'GET /statements/{invoiceNumber}/csv': 'download_statement_csv',
|
|
63
|
+
'GET /statements/export/csv': 'download_statement_csv',
|
|
64
|
+
'GET /statements/{invoiceNumber}/invoice': 'download_statement_invoice',
|
|
65
|
+
'GET /transactions': 'list_transactions',
|
|
66
|
+
'GET /royalties/breakdown': 'get_royalties_breakdown',
|
|
67
|
+
'GET /royalties/artificial-streams': 'list_artificial_streams',
|
|
68
|
+
'GET /artificial-streaming-fee/{period}': 'get_artificial_fee_breakdown',
|
|
69
|
+
// webhooks
|
|
70
|
+
'GET /webhooks': 'list_webhooks',
|
|
71
|
+
'POST /webhooks': 'create_webhook',
|
|
72
|
+
'GET /webhooks/event-types': 'list_webhook_event_types',
|
|
73
|
+
'GET /webhooks/{webhook}': 'get_webhook',
|
|
74
|
+
'PATCH /webhooks/{webhook}': 'update_webhook',
|
|
75
|
+
'DELETE /webhooks/{webhook}': 'delete_webhook',
|
|
76
|
+
'GET /webhooks/{webhook}/logs': 'get_webhook_logs',
|
|
77
|
+
'POST /webhooks/{webhook}/regenerate-secret': 'rotate_webhook_secret',
|
|
78
|
+
'POST /webhooks/{webhook}/test': 'test_webhook',
|
|
79
|
+
// catalog writes
|
|
80
|
+
'POST /labels': 'create_label',
|
|
81
|
+
'PATCH /labels/{label}': 'update_label',
|
|
82
|
+
'DELETE /labels/{label}': 'delete_label',
|
|
83
|
+
'POST /labels/{label}/images/{imageType}': 'upload_label_image',
|
|
84
|
+
'POST /artists': 'create_artist',
|
|
85
|
+
'PATCH /artists/{artist}': 'update_artist',
|
|
86
|
+
'DELETE /artists/{artist}': 'delete_artist',
|
|
87
|
+
'POST /artists/{artist}/photo': 'upload_artist_photo',
|
|
88
|
+
'POST /writers': 'create_writer',
|
|
89
|
+
'PATCH /writers/{writer}': 'update_writer',
|
|
90
|
+
'DELETE /writers/{writer}': 'delete_writer',
|
|
91
|
+
'POST /publishers': 'create_publisher',
|
|
92
|
+
'PATCH /publishers/{publisher}': 'update_publisher',
|
|
93
|
+
'DELETE /publishers/{publisher}': 'delete_publisher',
|
|
94
|
+
// release/track draft writes
|
|
95
|
+
'POST /releases': 'create_release',
|
|
96
|
+
'PATCH /releases/{release}': 'update_release',
|
|
97
|
+
'DELETE /releases/{release}': 'delete_release',
|
|
98
|
+
'POST /tracks': 'create_track',
|
|
99
|
+
'PATCH /tracks/{track}': 'update_track',
|
|
100
|
+
'DELETE /tracks/{track}': 'delete_track',
|
|
101
|
+
'POST /releases/{release}/validate': 'validate_release',
|
|
102
|
+
'POST /releases/{release}/quality-report/refresh': 'refresh_quality_report',
|
|
103
|
+
'PUT /releases/{release}/landing-config': 'update_landing_config',
|
|
104
|
+
'POST /releases/short-url': 'create_release_short_url',
|
|
105
|
+
'POST /review-issues/{reviewReleaseIssue}/notes': 'add_review_issue_note',
|
|
106
|
+
// full writes (distribution)
|
|
107
|
+
'POST /tracks/{track}/files/{fileType}/upload-url': 'upload_track_audio',
|
|
108
|
+
'PUT /tracks/{track}/files/{fileType}': 'upload_track_audio',
|
|
109
|
+
'DELETE /tracks/{track}/files/{fileType}': 'delete_track_audio',
|
|
110
|
+
'POST /releases/{release}/files/{assetType}/upload-url': 'upload_release_asset',
|
|
111
|
+
'PUT /releases/{release}/files/{assetType}': 'upload_release_asset',
|
|
112
|
+
'DELETE /releases/{release}/files/{assetType}': 'delete_release_asset',
|
|
113
|
+
'POST /tracks/{track}/licenses': 'upload_track_license',
|
|
114
|
+
'POST /tracks/{track}/licenses/{trackLicense}': 'update_track_license',
|
|
115
|
+
'DELETE /tracks/{track}/licenses/{trackLicense}': 'delete_track_license',
|
|
116
|
+
'POST /releases/{release}/photo': 'upload_release_artwork',
|
|
117
|
+
'POST /releases/{release}/distribute': 'distribute_release',
|
|
118
|
+
'POST /releases/{release}/takedown-all': 'takedown_release',
|
|
119
|
+
'POST /releases/{release}/confirm-review': 'confirm_review',
|
|
120
|
+
'POST /labels/{label}/enable-beatport': 'enable_beatport',
|
|
121
|
+
};
|
|
122
|
+
export const EXCLUDED = {
|
|
123
|
+
// The per-metric analytics endpoints are all served by get_analytics.
|
|
124
|
+
'GET /analytics/streams': 'served by get_analytics (summary)',
|
|
125
|
+
'GET /analytics/listeners': 'served by get_analytics (summary)',
|
|
126
|
+
'GET /analytics/saves': 'served by get_analytics (summary)',
|
|
127
|
+
'GET /analytics/skips': 'served by get_analytics (summary)',
|
|
128
|
+
'GET /analytics/shares': 'served by get_analytics (summary)',
|
|
129
|
+
'GET /analytics/completion-rate': 'served by get_analytics (summary)',
|
|
130
|
+
'GET /analytics/lyrics-view-rate': 'served by get_analytics (summary)',
|
|
131
|
+
'GET /analytics/canvas-view-rate': 'served by get_analytics (summary)',
|
|
132
|
+
'GET /analytics/device-split': 'served by get_analytics (summary)',
|
|
133
|
+
'GET /analytics/source-split': 'served by get_analytics (summary)',
|
|
134
|
+
'GET /analytics/saves-by-tier': 'served by get_analytics (summary)',
|
|
135
|
+
'GET /analytics/streams-by-country': 'served by get_analytics (summary)',
|
|
136
|
+
'GET /analytics/streams-by-gender': 'served by get_analytics (summary)',
|
|
137
|
+
'GET /analytics/streams-by-age': 'served by get_analytics (summary)',
|
|
138
|
+
'GET /analytics/shares-by-country': 'served by get_analytics (summary)',
|
|
139
|
+
// Alternate/adjacent surfaces intentionally not exposed in v1.
|
|
140
|
+
'POST /releases/{release}/withdraw-review': 'withdraw-review flow — not exposed in v1',
|
|
141
|
+
'GET /resolve/label/{labelSlug}': 'label-website resolution — not exposed in v1',
|
|
142
|
+
'GET /site-settings/{label}': 'label-website settings — not exposed in v1',
|
|
143
|
+
'GET /site-settings/links/{label}': 'label-website settings — not exposed in v1',
|
|
144
|
+
'GET /tracks/{track}/licenses/{trackLicense}/download': 'license file download — not exposed in v1',
|
|
145
|
+
'GET /transactions/csv': 'transaction CSV export — not exposed in v1',
|
|
146
|
+
};
|
|
147
|
+
export const PENDING_DOCS = {
|
|
148
|
+
'GET /account': 'get_account_summary',
|
|
149
|
+
'GET /tracks/{track}/files/{assetType}/download-url': 'get_track_audio_download_url',
|
|
150
|
+
};
|
package/dist/gating.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fail-closed tool gating.
|
|
3
|
+
*
|
|
4
|
+
* A tool is enabled only when its toolset is selected AND its gate's write
|
|
5
|
+
* class is armed. Anything the matrix cannot positively resolve — including an
|
|
6
|
+
* unrecognized gate — is disabled.
|
|
7
|
+
*/
|
|
8
|
+
import type { Config } from './config.js';
|
|
9
|
+
export type Gate = 'read' | 'safe_write' | 'full_write';
|
|
10
|
+
export declare function isToolEnabled(t: {
|
|
11
|
+
gate: Gate;
|
|
12
|
+
toolset: string;
|
|
13
|
+
}, c: Config): boolean;
|
package/dist/gating.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fail-closed tool gating.
|
|
3
|
+
*
|
|
4
|
+
* A tool is enabled only when its toolset is selected AND its gate's write
|
|
5
|
+
* class is armed. Anything the matrix cannot positively resolve — including an
|
|
6
|
+
* unrecognized gate — is disabled.
|
|
7
|
+
*/
|
|
8
|
+
export function isToolEnabled(t, c) {
|
|
9
|
+
const toolsetSelected = c.toolsets === null || c.toolsets.has(t.toolset);
|
|
10
|
+
if (!toolsetSelected)
|
|
11
|
+
return false;
|
|
12
|
+
switch (t.gate) {
|
|
13
|
+
case 'read':
|
|
14
|
+
return true;
|
|
15
|
+
case 'safe_write':
|
|
16
|
+
return c.writes;
|
|
17
|
+
case 'full_write':
|
|
18
|
+
return c.fullWrites;
|
|
19
|
+
default:
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Entrypoint: env → config → client → server → stdio.
|
|
4
|
+
*
|
|
5
|
+
* A configuration problem (missing token, etc.) prints an actionable message
|
|
6
|
+
* to stderr and exits 1. Otherwise the server connects over the stdio
|
|
7
|
+
* transport; stdout carries the MCP protocol only, all logging goes to stderr.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Entrypoint: env → config → client → server → stdio.
|
|
4
|
+
*
|
|
5
|
+
* A configuration problem (missing token, etc.) prints an actionable message
|
|
6
|
+
* to stderr and exits 1. Otherwise the server connects over the stdio
|
|
7
|
+
* transport; stdout carries the MCP protocol only, all logging goes to stderr.
|
|
8
|
+
*/
|
|
9
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
10
|
+
import { LabelGridClient } from './api/http.js';
|
|
11
|
+
import { ConfigError, loadConfig } from './config.js';
|
|
12
|
+
import { isToolEnabled } from './gating.js';
|
|
13
|
+
import { FULL_WRITES_NOTICE, LEGAL_SUMMARY } from './legal.js';
|
|
14
|
+
import { log } from './log.js';
|
|
15
|
+
import { buildServer } from './server.js';
|
|
16
|
+
import { accountingTools } from './tools/accounting.js';
|
|
17
|
+
import { analyticsTools } from './tools/analytics.js';
|
|
18
|
+
import { catalogReadTools } from './tools/catalog-read.js';
|
|
19
|
+
import { catalogWriteTools } from './tools/catalog-write.js';
|
|
20
|
+
import { deliveryTools } from './tools/delivery.js';
|
|
21
|
+
import { filesReadTools } from './tools/files-read.js';
|
|
22
|
+
import { fullWriteTools } from './tools/full-writes.js';
|
|
23
|
+
import { identityTools } from './tools/identity.js';
|
|
24
|
+
import { referenceTools } from './tools/reference.js';
|
|
25
|
+
import { releaseWriteTools } from './tools/release-write.js';
|
|
26
|
+
import { reviewReadTools } from './tools/review-read.js';
|
|
27
|
+
import { webhookTools } from './tools/webhooks.js';
|
|
28
|
+
import { VERSION } from './version.js';
|
|
29
|
+
function allTools() {
|
|
30
|
+
return [
|
|
31
|
+
...identityTools,
|
|
32
|
+
...referenceTools,
|
|
33
|
+
...analyticsTools,
|
|
34
|
+
...catalogReadTools,
|
|
35
|
+
...filesReadTools,
|
|
36
|
+
...reviewReadTools,
|
|
37
|
+
...deliveryTools,
|
|
38
|
+
...accountingTools,
|
|
39
|
+
...webhookTools,
|
|
40
|
+
...catalogWriteTools,
|
|
41
|
+
...releaseWriteTools,
|
|
42
|
+
...fullWriteTools,
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
async function main() {
|
|
46
|
+
let config;
|
|
47
|
+
try {
|
|
48
|
+
config = loadConfig(process.env);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
if (err instanceof ConfigError) {
|
|
52
|
+
log('error', err.message);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
// In setup mode the token is null and never used; the placeholder keeps the
|
|
58
|
+
// client type intact while no API calls are possible.
|
|
59
|
+
const client = new LabelGridClient({
|
|
60
|
+
baseUrl: config.baseUrl,
|
|
61
|
+
token: config.token ?? '',
|
|
62
|
+
version: VERSION,
|
|
63
|
+
});
|
|
64
|
+
if (config.setupMode) {
|
|
65
|
+
const server = buildServer(config, client, []);
|
|
66
|
+
log('info', `labelgrid-mcp v${VERSION} — setup mode (no API token configured); call the "setup" tool for guided setup`);
|
|
67
|
+
log('info', LEGAL_SUMMARY);
|
|
68
|
+
await server.connect(new StdioServerTransport());
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const tools = allTools();
|
|
72
|
+
const server = buildServer(config, client, tools);
|
|
73
|
+
const enabled = tools.filter((t) => isToolEnabled(t, config)).length;
|
|
74
|
+
log('info', `labelgrid-mcp v${VERSION} — ${enabled} tools enabled (writes: ${config.writes ? 'on' : 'off'}, full-writes: ${config.fullWrites ? 'on' : 'off'})`);
|
|
75
|
+
log('info', LEGAL_SUMMARY);
|
|
76
|
+
if (config.fullWrites) {
|
|
77
|
+
log('warn', FULL_WRITES_NOTICE);
|
|
78
|
+
}
|
|
79
|
+
await server.connect(new StdioServerTransport());
|
|
80
|
+
}
|
|
81
|
+
main().catch((err) => {
|
|
82
|
+
log('error', 'fatal error starting labelgrid-mcp', {
|
|
83
|
+
message: err instanceof Error ? err.message : String(err),
|
|
84
|
+
});
|
|
85
|
+
process.exit(1);
|
|
86
|
+
});
|
package/dist/legal.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The legal / acceptable-use disclosure strings, surfaced at runtime in two
|
|
3
|
+
* places: the MCP `instructions` field (shown by clients on initialize) and
|
|
4
|
+
* stderr at startup. README.md "Legal notices" carries the same text for
|
|
5
|
+
* readers who never launch the server.
|
|
6
|
+
*/
|
|
7
|
+
/** The one-paragraph AS-IS summary shown to every session. */
|
|
8
|
+
export declare const LEGAL_SUMMARY = "This software is provided AS-IS, without warranty of any kind, express or implied. By using it you accept sole responsibility for your use of the LabelGrid API and for every action taken by any AI client or agent you connect to this server, including write operations against your LabelGrid account. Your use of the API through this server is governed by the LabelGrid API Terms of Service and Acceptable Use Policy. This server does not bypass server-side protections such as rate limits, plan entitlements, or terms enforcement.";
|
|
9
|
+
/** Shown only when full writes are armed. */
|
|
10
|
+
export declare const FULL_WRITES_NOTICE = "Full writes are enabled. Distribution submissions, takedowns, and immutable file uploads initiated by an AI agent have real, potentially irreversible consequences for your releases on streaming platforms and stores. By setting the LABELGRID_FULL_WRITES_ACK acknowledgment variable you accepted that all such actions are your sole responsibility.";
|
|
11
|
+
/** The data-handling disclosure. */
|
|
12
|
+
export declare const DATA_HANDLING_NOTE = "This server transmits your LabelGrid catalogue and account data to the AI client you configure. Choosing that client, and disclosing that data flow where required, is your responsibility.";
|
package/dist/legal.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// README.md "Legal notices" mirrors these strings — keep them in sync when editing.
|
|
2
|
+
/**
|
|
3
|
+
* The legal / acceptable-use disclosure strings, surfaced at runtime in two
|
|
4
|
+
* places: the MCP `instructions` field (shown by clients on initialize) and
|
|
5
|
+
* stderr at startup. README.md "Legal notices" carries the same text for
|
|
6
|
+
* readers who never launch the server.
|
|
7
|
+
*/
|
|
8
|
+
/** The one-paragraph AS-IS summary shown to every session. */
|
|
9
|
+
export const LEGAL_SUMMARY = 'This software is provided AS-IS, without warranty of any kind, express or implied. By using it you accept sole responsibility for your use of the LabelGrid API and for every action taken by any AI client or agent you connect to this server, including write operations against your LabelGrid account. Your use of the API through this server is governed by the LabelGrid API Terms of Service and Acceptable Use Policy. This server does not bypass server-side protections such as rate limits, plan entitlements, or terms enforcement.';
|
|
10
|
+
/** Shown only when full writes are armed. */
|
|
11
|
+
export const FULL_WRITES_NOTICE = 'Full writes are enabled. Distribution submissions, takedowns, and immutable file uploads initiated by an AI agent have real, potentially irreversible consequences for your releases on streaming platforms and stores. By setting the LABELGRID_FULL_WRITES_ACK acknowledgment variable you accepted that all such actions are your sole responsibility.';
|
|
12
|
+
/** The data-handling disclosure. */
|
|
13
|
+
export const DATA_HANDLING_NOTE = 'This server transmits your LabelGrid catalogue and account data to the AI client you configure. Choosing that client, and disclosing that data flow where required, is your responsibility.';
|
package/dist/log.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stderr-only structured logging with secret redaction.
|
|
3
|
+
*
|
|
4
|
+
* stdout is reserved for the MCP protocol stream, so every log line goes to
|
|
5
|
+
* stderr. Any structured metadata is passed through {@link redactSecrets}
|
|
6
|
+
* first so tokens, passwords and signed URLs never reach the log.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Deep-clones a value, replacing the value of any object key whose name looks
|
|
10
|
+
* like a secret with a fixed mask. Non-secret values, arrays and primitives are
|
|
11
|
+
* preserved (arrays and nested objects are walked recursively).
|
|
12
|
+
*/
|
|
13
|
+
export declare function redactSecrets(v: unknown): unknown;
|
|
14
|
+
export type LogLevel = 'info' | 'warn' | 'error';
|
|
15
|
+
/** Writes a single redacted log line to stderr (never stdout). */
|
|
16
|
+
export declare function log(level: LogLevel, msg: string, meta?: unknown): void;
|
package/dist/log.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stderr-only structured logging with secret redaction.
|
|
3
|
+
*
|
|
4
|
+
* stdout is reserved for the MCP protocol stream, so every log line goes to
|
|
5
|
+
* stderr. Any structured metadata is passed through {@link redactSecrets}
|
|
6
|
+
* first so tokens, passwords and signed URLs never reach the log.
|
|
7
|
+
*/
|
|
8
|
+
const SECRET_KEY = /token|password|secret|nonce|authorization|key/i;
|
|
9
|
+
const MASK = '***REDACTED***';
|
|
10
|
+
/**
|
|
11
|
+
* Deep-clones a value, replacing the value of any object key whose name looks
|
|
12
|
+
* like a secret with a fixed mask. Non-secret values, arrays and primitives are
|
|
13
|
+
* preserved (arrays and nested objects are walked recursively).
|
|
14
|
+
*/
|
|
15
|
+
export function redactSecrets(v) {
|
|
16
|
+
if (Array.isArray(v)) {
|
|
17
|
+
return v.map((item) => redactSecrets(item));
|
|
18
|
+
}
|
|
19
|
+
if (v !== null && typeof v === 'object') {
|
|
20
|
+
const out = {};
|
|
21
|
+
for (const [key, value] of Object.entries(v)) {
|
|
22
|
+
out[key] = SECRET_KEY.test(key) ? MASK : redactSecrets(value);
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
return v;
|
|
27
|
+
}
|
|
28
|
+
/** Writes a single redacted log line to stderr (never stdout). */
|
|
29
|
+
export function log(level, msg, meta) {
|
|
30
|
+
let line = `[${level}] ${msg}`;
|
|
31
|
+
if (meta !== undefined) {
|
|
32
|
+
line += ` ${JSON.stringify(redactSecrets(meta))}`;
|
|
33
|
+
}
|
|
34
|
+
process.stderr.write(`${line}\n`);
|
|
35
|
+
}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP server construction and gated tool registration.
|
|
3
|
+
*
|
|
4
|
+
* Only tools that pass {@link isToolEnabled} are registered. Each handler is
|
|
5
|
+
* wrapped so it: (1) re-checks its gate at call time (defense in depth — the
|
|
6
|
+
* registration filter is the first line), (2) runs the one-call handler, (3)
|
|
7
|
+
* logs the tool name, redacted args and duration to stderr, and (4) shapes the
|
|
8
|
+
* result via {@link toToolResult} (API errors become isError results, never
|
|
9
|
+
* protocol errors).
|
|
10
|
+
*/
|
|
11
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
12
|
+
import type { LabelGridClient } from './api/http.js';
|
|
13
|
+
import type { Config } from './config.js';
|
|
14
|
+
import { type ToolDef } from './tools/types.js';
|
|
15
|
+
export declare function buildServer(config: Config, client: LabelGridClient, tools: ToolDef[]): McpServer;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP server construction and gated tool registration.
|
|
3
|
+
*
|
|
4
|
+
* Only tools that pass {@link isToolEnabled} are registered. Each handler is
|
|
5
|
+
* wrapped so it: (1) re-checks its gate at call time (defense in depth — the
|
|
6
|
+
* registration filter is the first line), (2) runs the one-call handler, (3)
|
|
7
|
+
* logs the tool name, redacted args and duration to stderr, and (4) shapes the
|
|
8
|
+
* result via {@link toToolResult} (API errors become isError results, never
|
|
9
|
+
* protocol errors).
|
|
10
|
+
*/
|
|
11
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
12
|
+
import { isToolEnabled } from './gating.js';
|
|
13
|
+
import { DATA_HANDLING_NOTE, FULL_WRITES_NOTICE, LEGAL_SUMMARY } from './legal.js';
|
|
14
|
+
import { log } from './log.js';
|
|
15
|
+
import { setupTools } from './tools/setup.js';
|
|
16
|
+
import { toToolResult } from './tools/types.js';
|
|
17
|
+
import { VERSION } from './version.js';
|
|
18
|
+
/**
|
|
19
|
+
* The MCP-native disclosure channel: a concise usage + legal text delivered in
|
|
20
|
+
* the initialize result's `instructions` field, which clients surface to users.
|
|
21
|
+
* In setup mode it points the client at the `setup` tool instead.
|
|
22
|
+
*/
|
|
23
|
+
function buildInstructions(config) {
|
|
24
|
+
if (config.setupMode) {
|
|
25
|
+
return [
|
|
26
|
+
'No LabelGrid API token is configured, so the account is not connected yet. ' +
|
|
27
|
+
'Call the `setup` tool for step-by-step instructions to connect. No account ' +
|
|
28
|
+
'data can be accessed in this state.',
|
|
29
|
+
LEGAL_SUMMARY,
|
|
30
|
+
].join('\n\n');
|
|
31
|
+
}
|
|
32
|
+
const parts = [
|
|
33
|
+
'Official LabelGrid MCP server — your AI client can read and manage this LabelGrid ' +
|
|
34
|
+
'account via the public API.',
|
|
35
|
+
LEGAL_SUMMARY,
|
|
36
|
+
DATA_HANDLING_NOTE,
|
|
37
|
+
];
|
|
38
|
+
if (config.fullWrites)
|
|
39
|
+
parts.push(FULL_WRITES_NOTICE);
|
|
40
|
+
return parts.join('\n\n');
|
|
41
|
+
}
|
|
42
|
+
export function buildServer(config, client, tools) {
|
|
43
|
+
const server = new McpServer({ name: 'labelgrid-mcp', version: VERSION }, { instructions: buildInstructions(config) });
|
|
44
|
+
// In setup mode ONLY the setup helper is registered — the account is not
|
|
45
|
+
// connected, so none of the API-backed tools are exposed.
|
|
46
|
+
const registered = config.setupMode ? setupTools : tools;
|
|
47
|
+
for (const tool of registered) {
|
|
48
|
+
if (!isToolEnabled(tool, config))
|
|
49
|
+
continue;
|
|
50
|
+
server.registerTool(tool.name, {
|
|
51
|
+
title: tool.title,
|
|
52
|
+
description: tool.description,
|
|
53
|
+
inputSchema: tool.inputShape,
|
|
54
|
+
annotations: { title: tool.title, ...tool.annotations },
|
|
55
|
+
}, async (args) => {
|
|
56
|
+
// Defense in depth: even a registered tool re-verifies its gate.
|
|
57
|
+
if (!isToolEnabled(tool, config)) {
|
|
58
|
+
return toToolResult({
|
|
59
|
+
error: {
|
|
60
|
+
code: 'TOOL_DISABLED',
|
|
61
|
+
message: `Tool "${tool.name}" is not enabled in the current configuration.`,
|
|
62
|
+
status: 0,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
const startedAt = Date.now();
|
|
67
|
+
let result;
|
|
68
|
+
try {
|
|
69
|
+
result = await tool.handler(args ?? {}, { client, config });
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
const message = err instanceof Error ? err.message : 'An unexpected error occurred.';
|
|
73
|
+
log('error', `tool ${tool.name} threw`, { message });
|
|
74
|
+
return toToolResult({
|
|
75
|
+
error: { code: 'UNEXPECTED_ERROR', message, status: 0 },
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
log('info', `tool ${tool.name}`, { args: args ?? {}, duration_ms: Date.now() - startedAt });
|
|
79
|
+
return toToolResult(result);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return server;
|
|
83
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
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[];
|