@labelgrid/mcp 0.2.2 → 0.3.1

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.
Files changed (62) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +132 -100
  3. package/dist/config.d.ts +15 -0
  4. package/dist/config.js +38 -6
  5. package/dist/coverage.js +79 -79
  6. package/dist/gating.d.ts +1 -1
  7. package/dist/gating.js +5 -1
  8. package/dist/index.js +1 -2
  9. package/dist/projection.d.ts +40 -0
  10. package/dist/projection.js +145 -0
  11. package/dist/resources.d.ts +29 -0
  12. package/dist/resources.js +107 -0
  13. package/dist/server.d.ts +2 -2
  14. package/dist/server.js +24 -4
  15. package/dist/tools/account.d.ts +3 -0
  16. package/dist/tools/account.js +32 -0
  17. package/dist/tools/all.js +12 -20
  18. package/dist/tools/catalog.d.ts +14 -0
  19. package/dist/tools/catalog.js +266 -0
  20. package/dist/tools/distribution.d.ts +13 -0
  21. package/dist/tools/distribution.js +265 -0
  22. package/dist/tools/finance.d.ts +12 -0
  23. package/dist/tools/finance.js +311 -0
  24. package/dist/tools/insights.d.ts +7 -0
  25. package/dist/tools/insights.js +139 -0
  26. package/dist/tools/reference.js +9 -24
  27. package/dist/tools/releases.d.ts +11 -0
  28. package/dist/tools/releases.js +177 -0
  29. package/dist/tools/setup.js +1 -1
  30. package/dist/tools/types.d.ts +1 -1
  31. package/dist/tools/webhooks.d.ts +3 -3
  32. package/dist/tools/webhooks.js +73 -105
  33. package/package.json +5 -14
  34. package/server.json +3 -3
  35. package/dist/api/content-types.d.ts +0 -33
  36. package/dist/api/content-types.js +0 -87
  37. package/dist/api/http.d.ts +0 -74
  38. package/dist/api/http.js +0 -392
  39. package/dist/api/upload.d.ts +0 -26
  40. package/dist/api/upload.js +0 -104
  41. package/dist/log.d.ts +0 -16
  42. package/dist/log.js +0 -35
  43. package/dist/tools/accounting.d.ts +0 -12
  44. package/dist/tools/accounting.js +0 -386
  45. package/dist/tools/analytics.d.ts +0 -3
  46. package/dist/tools/analytics.js +0 -62
  47. package/dist/tools/catalog-read.d.ts +0 -10
  48. package/dist/tools/catalog-read.js +0 -145
  49. package/dist/tools/catalog-write.d.ts +0 -12
  50. package/dist/tools/catalog-write.js +0 -206
  51. package/dist/tools/delivery.d.ts +0 -6
  52. package/dist/tools/delivery.js +0 -40
  53. package/dist/tools/files-read.d.ts +0 -7
  54. package/dist/tools/files-read.js +0 -86
  55. package/dist/tools/full-writes.d.ts +0 -12
  56. package/dist/tools/full-writes.js +0 -248
  57. package/dist/tools/identity.d.ts +0 -3
  58. package/dist/tools/identity.js +0 -28
  59. package/dist/tools/release-write.d.ts +0 -12
  60. package/dist/tools/release-write.js +0 -184
  61. package/dist/tools/review-read.d.ts +0 -7
  62. 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
- // identity
19
- 'GET /me': '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
- // analytics
30
+ // insights
31
31
  'GET /analytics/summary': 'get_analytics',
32
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',
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}': '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',
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
- // 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',
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': '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',
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': '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',
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': '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',
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': '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',
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': 'get_account_summary',
149
- 'GET /tracks/{track}/files/{assetType}/download-url': 'get_track_audio_download_url',
148
+ 'GET /account': 'get_account',
149
+ 'GET /tracks/{track}/files/{assetType}/download-url': 'get_asset',
150
150
  };
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 { Config } from './config.js';
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
- const toolsetSelected = c.toolsets === null || c.toolsets.has(t.toolset);
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) {
package/dist/index.js CHANGED
@@ -6,12 +6,11 @@
6
6
  * to stderr and exits 1. Otherwise the server connects over the stdio
7
7
  * transport; stdout carries the MCP protocol only, all logging goes to stderr.
8
8
  */
9
+ import { LabelGridClient, log } from '@labelgrid/core';
9
10
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
- import { LabelGridClient } from './api/http.js';
11
11
  import { ConfigError, loadConfig } from './config.js';
12
12
  import { isToolEnabled } from './gating.js';
13
13
  import { FULL_WRITES_NOTICE, LEGAL_SUMMARY } from './legal.js';
14
- import { log } from './log.js';
15
14
  import { buildServer } from './server.js';
16
15
  import { allTools } from './tools/all.js';
17
16
  import { VERSION } from './version.js';
@@ -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 '@labelgrid/core';
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 { LabelGridClient } from '@labelgrid/core';
13
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.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
@@ -8,8 +8,8 @@
8
8
  * result via {@link toToolResult} (API errors become isError results, never
9
9
  * protocol errors).
10
10
  */
11
+ import { type LabelGridClient } from '@labelgrid/core';
11
12
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
12
- import type { LabelGridClient } from './api/http.js';
13
- import type { Config } from './config.js';
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;
package/dist/server.js CHANGED
@@ -8,10 +8,12 @@
8
8
  * result via {@link toToolResult} (API errors become isError results, never
9
9
  * protocol errors).
10
10
  */
11
+ import { log } from '@labelgrid/core';
11
12
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
13
+ import { defaultExcludedToolsets } from './config.js';
12
14
  import { isToolEnabled } from './gating.js';
13
15
  import { DATA_HANDLING_NOTE, FULL_WRITES_NOTICE, LEGAL_SUMMARY } from './legal.js';
14
- import { log } from './log.js';
16
+ import { registerReferenceResources } from './resources.js';
15
17
  import { setupTools } from './tools/setup.js';
16
18
  import { toToolResult } from './tools/types.js';
17
19
  import { VERSION } from './version.js';
@@ -51,10 +53,14 @@ export function buildServer(config, client, tools) {
51
53
  for (const tool of registered) {
52
54
  const isSetupHelper = tool.toolset === 'setup';
53
55
  // Listing rule: connected mode applies the full gate matrix; setup mode
54
- // lists the whole catalog (only honoring an explicit toolset narrowing),
55
- // because nothing can execute without a token anyway.
56
+ // lists the whole catalog (honoring an explicit toolset narrowing, and
57
+ // otherwise the same default exclusion the connected surface applies — so
58
+ // the advertised surface matches reality), because nothing can execute
59
+ // without a token anyway.
56
60
  const listable = config.setupMode
57
- ? config.toolsets === null || config.toolsets.has(tool.toolset)
61
+ ? config.toolsets === null
62
+ ? !defaultExcludedToolsets.has(tool.toolset)
63
+ : config.toolsets.has(tool.toolset)
58
64
  : isToolEnabled(tool, config);
59
65
  if (!isSetupHelper && !listable)
60
66
  continue;
@@ -101,5 +107,19 @@ export function buildServer(config, client, tools) {
101
107
  return toToolResult(result);
102
108
  });
103
109
  }
110
+ // The nine labelgrid://reference/{type} resources follow the SAME listing
111
+ // rule as the reference tools: they are registered only when the `reference`
112
+ // toolset is enabled. Reference is not default-excluded, so an unset
113
+ // LABELGRID_TOOLSETS (or one naming `reference`) registers them; an explicit
114
+ // selection omitting `reference` disables them. Setup mode keeps them
115
+ // registered-but-inert under the same rule (reads return NOT_CONNECTED).
116
+ const referenceEnabled = config.setupMode
117
+ ? config.toolsets === null
118
+ ? !defaultExcludedToolsets.has('reference')
119
+ : config.toolsets.has('reference')
120
+ : isToolEnabled({ gate: 'read', toolset: 'reference' }, config);
121
+ if (referenceEnabled) {
122
+ registerReferenceResources(server, config, client);
123
+ }
104
124
  return server;
105
125
  }
@@ -0,0 +1,3 @@
1
+ /** Account toolset: read the authenticated account, revoke API tokens. */
2
+ import type { ToolDef } from './types.js';
3
+ export declare const accountTools: ToolDef[];