@labelgrid/mcp 0.2.1 → 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.
Files changed (56) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +133 -101
  3. package/dist/api/http.d.ts +12 -0
  4. package/dist/api/http.js +70 -23
  5. package/dist/config.d.ts +15 -0
  6. package/dist/config.js +37 -5
  7. package/dist/coverage.js +79 -79
  8. package/dist/entities.d.ts +25 -0
  9. package/dist/entities.js +51 -0
  10. package/dist/gating.d.ts +1 -1
  11. package/dist/gating.js +5 -1
  12. package/dist/projection.d.ts +40 -0
  13. package/dist/projection.js +145 -0
  14. package/dist/resources.d.ts +29 -0
  15. package/dist/resources.js +107 -0
  16. package/dist/server.d.ts +1 -1
  17. package/dist/server.js +23 -3
  18. package/dist/tools/account.d.ts +3 -0
  19. package/dist/tools/account.js +32 -0
  20. package/dist/tools/all.js +12 -20
  21. package/dist/tools/catalog.d.ts +14 -0
  22. package/dist/tools/catalog.js +267 -0
  23. package/dist/tools/distribution.d.ts +13 -0
  24. package/dist/tools/distribution.js +266 -0
  25. package/dist/tools/finance.d.ts +12 -0
  26. package/dist/tools/finance.js +311 -0
  27. package/dist/tools/insights.d.ts +7 -0
  28. package/dist/tools/insights.js +139 -0
  29. package/dist/tools/reference.js +9 -24
  30. package/dist/tools/releases.d.ts +11 -0
  31. package/dist/tools/releases.js +177 -0
  32. package/dist/tools/setup.js +1 -1
  33. package/dist/tools/webhooks.d.ts +3 -3
  34. package/dist/tools/webhooks.js +73 -105
  35. package/package.json +3 -2
  36. package/server.json +3 -3
  37. package/dist/tools/accounting.d.ts +0 -12
  38. package/dist/tools/accounting.js +0 -386
  39. package/dist/tools/analytics.d.ts +0 -3
  40. package/dist/tools/analytics.js +0 -62
  41. package/dist/tools/catalog-read.d.ts +0 -10
  42. package/dist/tools/catalog-read.js +0 -145
  43. package/dist/tools/catalog-write.d.ts +0 -12
  44. package/dist/tools/catalog-write.js +0 -206
  45. package/dist/tools/delivery.d.ts +0 -6
  46. package/dist/tools/delivery.js +0 -40
  47. package/dist/tools/files-read.d.ts +0 -7
  48. package/dist/tools/files-read.js +0 -86
  49. package/dist/tools/full-writes.d.ts +0 -12
  50. package/dist/tools/full-writes.js +0 -248
  51. package/dist/tools/identity.d.ts +0 -3
  52. package/dist/tools/identity.js +0 -28
  53. package/dist/tools/release-write.d.ts +0 -12
  54. package/dist/tools/release-write.js +0 -184
  55. package/dist/tools/review-read.d.ts +0 -7
  56. package/dist/tools/review-read.js +0 -78
package/dist/config.js CHANGED
@@ -13,17 +13,36 @@ export const DEFAULT_BASE_URL = 'https://api.labelgrid.com/api/public';
13
13
  export const FULL_WRITES_ACK = 'I accept responsibility for AI-driven distribution actions';
14
14
  /** The valid toolset names; unknown names in LABELGRID_TOOLSETS warn and are ignored. */
15
15
  export const KNOWN_TOOLSETS = new Set([
16
- 'identity',
16
+ 'account',
17
17
  'reference',
18
18
  'catalog',
19
19
  'releases',
20
- 'review',
21
- 'analytics',
22
- 'accounting',
23
- 'delivery',
20
+ 'insights',
21
+ 'finance',
24
22
  'webhooks',
25
23
  'distribution',
26
24
  ]);
25
+ /**
26
+ * Pre-0.3.0 toolset names still accepted in LABELGRID_TOOLSETS, translated to
27
+ * their current toolset. Every legacy name used emits a stderr warning naming
28
+ * the toolset it maps to (see loadConfig). Names that survived the regroup
29
+ * (catalog, reference, releases, webhooks, distribution) map to themselves via
30
+ * KNOWN_TOOLSETS and need no alias entry.
31
+ */
32
+ export const LEGACY_TOOLSET_ALIASES = {
33
+ identity: 'account',
34
+ review: 'releases',
35
+ delivery: 'releases',
36
+ analytics: 'insights',
37
+ accounting: 'finance',
38
+ };
39
+ /**
40
+ * Toolsets excluded from the default surface when LABELGRID_TOOLSETS is unset
41
+ * (`toolsets === null`). Naming one explicitly in LABELGRID_TOOLSETS enables
42
+ * it. Consulted by gating and by the setup-mode listing, so the advertised
43
+ * surface matches reality.
44
+ */
45
+ export const defaultExcludedToolsets = new Set(['webhooks']);
27
46
  /** Thrown when the environment cannot produce a usable config. */
28
47
  export class ConfigError extends Error {
29
48
  constructor(message) {
@@ -72,6 +91,19 @@ export function loadConfig(env) {
72
91
  .split(',')
73
92
  .map((s) => s.trim())
74
93
  .filter((s) => s.length > 0)) {
94
+ const alias = LEGACY_TOOLSET_ALIASES[name];
95
+ if (alias !== undefined) {
96
+ // Legacy names still work, but warn loudly so the operator migrates —
97
+ // and, for the review/delivery → releases remaps, flag that `releases`
98
+ // also carries write tools so a read-only expectation is not violated.
99
+ let warning = `Legacy toolset name "${name}" in LABELGRID_TOOLSETS maps to "${alias}" — update your configuration to use "${alias}".`;
100
+ if (name === 'review' || name === 'delivery') {
101
+ warning += ` Note: the mapped "${alias}" toolset also contains write tools (run_release_checks, manage_release_links, add_review_issue_note) — set LABELGRID_READ_ONLY=true or LABELGRID_ENABLE_WRITES=false to keep a read-only surface.`;
102
+ }
103
+ log('warn', warning);
104
+ toolsets.add(alias);
105
+ continue;
106
+ }
75
107
  if (!KNOWN_TOOLSETS.has(name)) {
76
108
  log('warn', `Unknown toolset in LABELGRID_TOOLSETS: "${name}" (ignored).`);
77
109
  }
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
  };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The catalog-entity registry: the six entity kinds the consolidated catalog
3
+ * tools operate on, each with its endpoint path and the reviewed documentation
4
+ * fragments (list filters, create/update fields, delete refusals) the tool
5
+ * descriptions are assembled from.
6
+ *
7
+ * This is data, not behavior — the catalog tools stay thin wrappers and the
8
+ * API owns all validation. The wording here carries the caveats from the
9
+ * per-entity tool descriptions it replaces (recording_country on track create,
10
+ * RELEASE_LOCKED_FIELDS on release update, the delete refusals).
11
+ */
12
+ export type EntityName = 'label' | 'artist' | 'writer' | 'publisher' | 'release' | 'track';
13
+ /** The entity names as a tuple, for zod enum inputs. */
14
+ export declare const ENTITY_NAMES: readonly ["label", "artist", "writer", "publisher", "release", "track"];
15
+ export type EntitySpec = {
16
+ /** The collection endpoint path, e.g. '/labels'. */
17
+ path: string;
18
+ /** One-line doc of the useful list filters for search_catalog. */
19
+ filtersDoc: string;
20
+ /** One-line doc of required + common create/update fields. */
21
+ fieldsDoc: string;
22
+ /** One-line doc of the server-side delete refusals. */
23
+ deleteNote: string;
24
+ };
25
+ export declare const ENTITIES: Record<EntityName, EntitySpec>;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The catalog-entity registry: the six entity kinds the consolidated catalog
3
+ * tools operate on, each with its endpoint path and the reviewed documentation
4
+ * fragments (list filters, create/update fields, delete refusals) the tool
5
+ * descriptions are assembled from.
6
+ *
7
+ * This is data, not behavior — the catalog tools stay thin wrappers and the
8
+ * API owns all validation. The wording here carries the caveats from the
9
+ * per-entity tool descriptions it replaces (recording_country on track create,
10
+ * RELEASE_LOCKED_FIELDS on release update, the delete refusals).
11
+ */
12
+ /** The entity names as a tuple, for zod enum inputs. */
13
+ export const ENTITY_NAMES = ['label', 'artist', 'writer', 'publisher', 'release', 'track'];
14
+ export const ENTITIES = {
15
+ label: {
16
+ path: '/labels',
17
+ filtersDoc: 'label: no documented filters — paginate with page/per_page.',
18
+ fieldsDoc: 'label — required: name, default_email; optional: support email, website/platform URLs, default copyright lines, isrc_base.',
19
+ deleteNote: 'label: refused while the label still has releases — remove or reassign its releases first.',
20
+ },
21
+ artist: {
22
+ path: '/artists',
23
+ filtersDoc: 'artist: artist_name (filter by artist name).',
24
+ fieldsDoc: 'artist — required: artist_name; optional: full_name, email, location, bios, isni, default_language, platform profile URLs.',
25
+ deleteNote: 'artist: refused while still referenced by releases or tracks.',
26
+ },
27
+ writer: {
28
+ path: '/writers',
29
+ filtersDoc: 'writer: name (writer name), ipi (IPI number).',
30
+ fieldsDoc: 'writer — required: first_name, last_name; optional: middle_name, display_credits, email, country, pro, ipi, isni, publisher_id (or publisher_name/publisher_pro/publisher_ipi).',
31
+ deleteNote: 'writer: refused while still referenced by tracks.',
32
+ },
33
+ publisher: {
34
+ path: '/publishers',
35
+ filtersDoc: 'publisher: name (publisher name), ipi (IPI number).',
36
+ fieldsDoc: 'publisher — required: name; optional: ipi, pro, isni, controlled_publisher.',
37
+ deleteNote: 'publisher: refused while still referenced by writers.',
38
+ },
39
+ release: {
40
+ path: '/releases',
41
+ filtersDoc: 'release: label_id (owning label id), is_live (1 = live/distributed only), barcode_number (UPC/EAN), cat (catalog number).',
42
+ fieldsDoc: 'release — required on create: content_type, label_id, artists, titles, cat (catalog number), artwork_ai_usage, primary_genre_id; many optional fields (dates, copyright lines, genres, per-outlet URLs). Once submitted or distributed some fields are locked — changing one returns a 403 with code RELEASE_LOCKED_FIELDS naming exactly which fields cannot change.',
43
+ deleteNote: 'release: only a never-submitted draft can be deleted.',
44
+ },
45
+ track: {
46
+ path: '/tracks',
47
+ filtersDoc: 'track: release_id (one release’s tracks), isrc (filter by ISRC).',
48
+ fieldsDoc: 'track — required on create: release_id, disc, track_num, composition_type, artists, audio_ai_usage, composition_ai_usage, commercial_samples, audio_language, contributors, and recording_country (ISO 3166-1 alpha-2, e.g. "US"); optional: titles, isrc, iswc, writers, publishers, splits, and more.',
49
+ deleteNote: 'track: allowed while the parent release is an editable draft; refused once submitted or distributed.',
50
+ },
51
+ };
package/dist/gating.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * class is armed. Anything the matrix cannot positively resolve — including an
6
6
  * unrecognized gate — is disabled.
7
7
  */
8
- import type { 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) {
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Concise-mode field projection.
3
+ *
4
+ * Tools marked [proj] accept `response_format: 'concise' | 'detailed'`
5
+ * (default concise). Concise mode projects the API response down to a per-tool
6
+ * allowlist of high-signal fields so large payloads stop swamping the client's
7
+ * context window; `'detailed'` bypasses projection and returns the verbatim
8
+ * API response.
9
+ *
10
+ * The walk keeps, on every object: any key in the per-tool allowlist, ALWAYS
11
+ * `id` and any key ending `_id`, and any key whose value is an object/array
12
+ * (containers are what the walk traverses — this is what keeps pagination
13
+ * envelopes like `{ data: [...] }` intact while their leaves are filtered).
14
+ * Kept values are recursed into. A `"_projection": "concise"` marker is
15
+ * appended at the TOP level only (when the top level is an object).
16
+ *
17
+ * Pagination fidelity: a subtree keyed exactly `meta` or `links` (at ANY depth)
18
+ * is preserved VERBATIM — every field, unfiltered — so page counts, totals and
19
+ * next/prev links always survive. Top-level primitive cursor keys
20
+ * (`next_cursor`, `prev_cursor`, `cursor`) are likewise preserved so
21
+ * cursor-paginated responses stay pageable under concise mode.
22
+ *
23
+ * Projection is presentation-only — it never transforms values. The 400K
24
+ * toToolResult cap stays as the backstop for anything projection cannot tame.
25
+ */
26
+ import type { ApiResult } from './api/http.js';
27
+ /** Per-tool concise-mode field allowlists, keyed by tool name. */
28
+ export declare const CONCISE_ALLOWLISTS: Record<string, readonly string[]>;
29
+ /**
30
+ * Projects `value` down to the allowlisted fields (plus `id`/`*_id` and
31
+ * container keys), appending the `_projection: 'concise'` marker at the top
32
+ * level when the top level is an object.
33
+ */
34
+ export declare function projectConcise(value: unknown, keep: readonly string[]): unknown;
35
+ /**
36
+ * Applies a [proj] tool's concise projection to a successful result. Errors
37
+ * pass through untouched; `response_format: 'detailed'` bypasses projection
38
+ * entirely (the verbatim API response); absent or `'concise'` projects.
39
+ */
40
+ export declare function applyProjection(result: ApiResult<unknown>, toolName: string, responseFormat: unknown): ApiResult<unknown>;
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Concise-mode field projection.
3
+ *
4
+ * Tools marked [proj] accept `response_format: 'concise' | 'detailed'`
5
+ * (default concise). Concise mode projects the API response down to a per-tool
6
+ * allowlist of high-signal fields so large payloads stop swamping the client's
7
+ * context window; `'detailed'` bypasses projection and returns the verbatim
8
+ * API response.
9
+ *
10
+ * The walk keeps, on every object: any key in the per-tool allowlist, ALWAYS
11
+ * `id` and any key ending `_id`, and any key whose value is an object/array
12
+ * (containers are what the walk traverses — this is what keeps pagination
13
+ * envelopes like `{ data: [...] }` intact while their leaves are filtered).
14
+ * Kept values are recursed into. A `"_projection": "concise"` marker is
15
+ * appended at the TOP level only (when the top level is an object).
16
+ *
17
+ * Pagination fidelity: a subtree keyed exactly `meta` or `links` (at ANY depth)
18
+ * is preserved VERBATIM — every field, unfiltered — so page counts, totals and
19
+ * next/prev links always survive. Top-level primitive cursor keys
20
+ * (`next_cursor`, `prev_cursor`, `cursor`) are likewise preserved so
21
+ * cursor-paginated responses stay pageable under concise mode.
22
+ *
23
+ * Projection is presentation-only — it never transforms values. The 400K
24
+ * toToolResult cap stays as the backstop for anything projection cannot tame.
25
+ */
26
+ /** The shared allowlist for catalog entity reads (search_catalog / get_catalog_item). */
27
+ const CATALOG_FIELDS = [
28
+ 'title',
29
+ 'name',
30
+ 'artist_name',
31
+ 'full_name',
32
+ 'status',
33
+ 'review_status',
34
+ 'is_live',
35
+ 'barcode_number',
36
+ 'cat',
37
+ 'isrc',
38
+ 'release_date',
39
+ 'created_at',
40
+ 'updated_at',
41
+ 'email',
42
+ 'ipi',
43
+ 'pro',
44
+ ];
45
+ /** Per-tool concise-mode field allowlists, keyed by tool name. */
46
+ export const CONCISE_ALLOWLISTS = {
47
+ search_catalog: CATALOG_FIELDS,
48
+ get_catalog_item: CATALOG_FIELDS,
49
+ get_release_review: [
50
+ 'code',
51
+ 'title',
52
+ 'severity',
53
+ 'status',
54
+ 'requires_feedback',
55
+ 'message',
56
+ 'created_at',
57
+ ],
58
+ get_delivery_queue: ['status', 'outlet', 'outlet_id', 'delivered_at', 'created_at', 'type'],
59
+ query_artificial_streaming: [
60
+ 'dsp',
61
+ 'country',
62
+ 'quantity',
63
+ 'period',
64
+ 'date',
65
+ 'status',
66
+ 'severity',
67
+ 'isrc',
68
+ 'upc',
69
+ ],
70
+ query_financials: [
71
+ 'period',
72
+ 'status',
73
+ 'currency',
74
+ 'gross_usd',
75
+ 'net_usd',
76
+ 'amount',
77
+ 'total_due_usd',
78
+ 'invoice_number',
79
+ 'transaction_type',
80
+ 'scope',
81
+ 'date_paid',
82
+ 'created_at',
83
+ ],
84
+ };
85
+ /** Subtrees whose entire contents are kept verbatim, at any depth. */
86
+ const VERBATIM_ENVELOPE_KEYS = new Set(['meta', 'links']);
87
+ /** Primitive cursor keys preserved at the TOP level only. */
88
+ const TOP_LEVEL_CURSOR_KEYS = new Set([
89
+ 'next_cursor',
90
+ 'prev_cursor',
91
+ 'cursor',
92
+ ]);
93
+ function isContainer(value) {
94
+ return value !== null && typeof value === 'object';
95
+ }
96
+ function projectValue(value, keep, topLevel = false) {
97
+ if (Array.isArray(value)) {
98
+ return value.map((item) => projectValue(item, keep));
99
+ }
100
+ if (isContainer(value)) {
101
+ const out = {};
102
+ for (const [key, item] of Object.entries(value)) {
103
+ // Pagination envelopes: a `meta`/`links` subtree is kept verbatim, unfiltered.
104
+ if (VERBATIM_ENVELOPE_KEYS.has(key)) {
105
+ out[key] = item;
106
+ continue;
107
+ }
108
+ // Top-level primitive cursor keys survive so pagination isn't lost.
109
+ if (topLevel && TOP_LEVEL_CURSOR_KEYS.has(key) && !isContainer(item)) {
110
+ out[key] = item;
111
+ continue;
112
+ }
113
+ if (keep.has(key) || key === 'id' || key.endsWith('_id') || isContainer(item)) {
114
+ out[key] = isContainer(item) ? projectValue(item, keep) : item;
115
+ }
116
+ }
117
+ return out;
118
+ }
119
+ return value;
120
+ }
121
+ /**
122
+ * Projects `value` down to the allowlisted fields (plus `id`/`*_id` and
123
+ * container keys), appending the `_projection: 'concise'` marker at the top
124
+ * level when the top level is an object.
125
+ */
126
+ export function projectConcise(value, keep) {
127
+ const keepSet = new Set(keep);
128
+ const projected = projectValue(value, keepSet, true);
129
+ if (isContainer(projected) && !Array.isArray(projected)) {
130
+ return { ...projected, _projection: 'concise' };
131
+ }
132
+ return projected;
133
+ }
134
+ /**
135
+ * Applies a [proj] tool's concise projection to a successful result. Errors
136
+ * pass through untouched; `response_format: 'detailed'` bypasses projection
137
+ * entirely (the verbatim API response); absent or `'concise'` projects.
138
+ */
139
+ export function applyProjection(result, toolName, responseFormat) {
140
+ if (responseFormat === 'detailed')
141
+ return result;
142
+ if ('error' in result)
143
+ return result;
144
+ return { data: projectConcise(result.data, CONCISE_ALLOWLISTS[toolName] ?? []) };
145
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * MCP resources: the nine reference datasets, exposed at
3
+ * `labelgrid://reference/{type}`.
4
+ *
5
+ * Each read fetches the dataset via the shared client (no caching) and returns
6
+ * JSON text. The same datasets are served by the `list_reference_data` tool,
7
+ * which is the fallback for clients that don't surface resources — the
8
+ * type→path map lives here as the single source for both. In setup mode the
9
+ * resources are still registered (so introspection shows what the server
10
+ * offers) but reads return the NOT_CONNECTED guidance JSON instead of data.
11
+ */
12
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
13
+ import type { LabelGridClient } from './api/http.js';
14
+ import type { Config } from './config.js';
15
+ /** The reference dataset types as a tuple, for the tool's zod enum. */
16
+ export declare const REFERENCE_TYPES: readonly ["genres", "genre_categories", "languages", "contributor_roles", "instruments", "distro_outlets", "territories", "issue_definitions", "webhook_event_types"];
17
+ export type ReferenceType = (typeof REFERENCE_TYPES)[number];
18
+ export declare const REFERENCE_DATASETS: Record<ReferenceType, {
19
+ path: string;
20
+ title: string;
21
+ description: string;
22
+ }>;
23
+ /** The URI for one reference dataset resource. */
24
+ export declare function referenceUri(type: ReferenceType): string;
25
+ /**
26
+ * Registers the nine `labelgrid://reference/{type}` resources on the server.
27
+ * Reads fetch via the client; in setup mode they return NOT_CONNECTED guidance.
28
+ */
29
+ export declare function registerReferenceResources(server: Pick<McpServer, 'registerResource'>, config: Config, client: LabelGridClient): void;