@withpica/mcp-server-directory 1.0.0 → 1.2.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 (75) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/client.d.ts.map +1 -1
  3. package/dist/client.js +1 -0
  4. package/dist/client.js.map +1 -1
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/config.js +1 -0
  7. package/dist/config.js.map +1 -1
  8. package/dist/index.js +1 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/prompts/index.d.ts.map +1 -1
  11. package/dist/prompts/index.js +9 -12
  12. package/dist/prompts/index.js.map +1 -1
  13. package/dist/prompts/public-question-atlas.d.ts +121 -0
  14. package/dist/prompts/public-question-atlas.d.ts.map +1 -0
  15. package/dist/prompts/public-question-atlas.js +404 -0
  16. package/dist/prompts/public-question-atlas.js.map +1 -0
  17. package/dist/resources/llms-primer.d.ts.map +1 -1
  18. package/dist/resources/llms-primer.js +1 -0
  19. package/dist/resources/llms-primer.js.map +1 -1
  20. package/dist/server.d.ts.map +1 -1
  21. package/dist/server.js +1 -0
  22. package/dist/server.js.map +1 -1
  23. package/dist/tools/chain.d.ts +12 -0
  24. package/dist/tools/chain.d.ts.map +1 -0
  25. package/dist/tools/chain.js +109 -0
  26. package/dist/tools/chain.js.map +1 -0
  27. package/dist/tools/index.d.ts +9 -0
  28. package/dist/tools/index.d.ts.map +1 -1
  29. package/dist/tools/index.js +3 -0
  30. package/dist/tools/index.js.map +1 -1
  31. package/dist/tools/people.d.ts +0 -1
  32. package/dist/tools/people.d.ts.map +1 -1
  33. package/dist/tools/people.js +24 -36
  34. package/dist/tools/people.js.map +1 -1
  35. package/dist/tools/recordings.d.ts.map +1 -1
  36. package/dist/tools/recordings.js +8 -3
  37. package/dist/tools/recordings.js.map +1 -1
  38. package/dist/tools/search.d.ts.map +1 -1
  39. package/dist/tools/search.js +8 -4
  40. package/dist/tools/search.js.map +1 -1
  41. package/dist/tools/works.d.ts +0 -1
  42. package/dist/tools/works.d.ts.map +1 -1
  43. package/dist/tools/works.js +42 -42
  44. package/dist/tools/works.js.map +1 -1
  45. package/dist/utils/errors.d.ts.map +1 -1
  46. package/dist/utils/errors.js +1 -0
  47. package/dist/utils/errors.js.map +1 -1
  48. package/dist/utils/formatting.d.ts.map +1 -1
  49. package/dist/utils/formatting.js +1 -0
  50. package/dist/utils/formatting.js.map +1 -1
  51. package/jest.config.js +31 -0
  52. package/package.json +3 -2
  53. package/src/__tests__/prompts/index.test.ts +128 -0
  54. package/src/__tests__/prompts/prompt-eval-harness.test.ts +282 -0
  55. package/src/__tests__/tools/chain.test.ts +122 -0
  56. package/src/__tests__/tools/composability-chains.test.ts +100 -0
  57. package/src/__tests__/tools/people.test.ts +112 -0
  58. package/src/__tests__/tools/search.test.ts +94 -0
  59. package/src/__tests__/tools/works.test.ts +177 -0
  60. package/src/client.ts +128 -0
  61. package/src/config.ts +23 -0
  62. package/src/index.ts +36 -0
  63. package/src/prompts/index.ts +206 -0
  64. package/src/prompts/public-question-atlas.ts +540 -0
  65. package/src/resources/llms-primer.ts +35 -0
  66. package/src/server.ts +134 -0
  67. package/src/tools/chain.ts +118 -0
  68. package/src/tools/index.ts +83 -0
  69. package/src/tools/people.ts +196 -0
  70. package/src/tools/recordings.ts +149 -0
  71. package/src/tools/search.ts +66 -0
  72. package/src/tools/works.ts +266 -0
  73. package/src/utils/errors.ts +64 -0
  74. package/src/utils/formatting.ts +28 -0
  75. package/tsconfig.json +22 -0
@@ -0,0 +1,540 @@
1
+ // Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.
2
+
3
+ /**
4
+ * Public Question Atlas — ADR-229 Decision 1.
5
+ *
6
+ * Source-of-truth registry mapping natural public-discovery questions to
7
+ * the canonical directory-MCP tool resolution. Sister of:
8
+ * - `mcp-server/src/prompts/creator-question-atlas.ts` (ADR-226, customer)
9
+ * - `team-mcp-server/src/prompts/operator-question-atlas.ts` (ADR-228, operator)
10
+ *
11
+ * Read by:
12
+ *
13
+ * - lint Rule 11F (extended in ADR-229) — validates every `directory_*`
14
+ * tool's `Use when the user asks` block cites questions resolving
15
+ * here, rejects creator/operator atlas cross-pollination, asserts
16
+ * mutual exclusivity of `clean_empty_state` and `fallback_when_empty`,
17
+ * and asserts `disambiguation` shape integrity
18
+ * - `scripts/intent-resolution-eval.ts --surface=directory` (ADR-229 PR-3)
19
+ * - the Phase 4 description rewrites — `Use when the user asks` blocks
20
+ * pull question + synonym strings from here
21
+ *
22
+ * The `AtlasEntry` type is inline-duplicated rather than imported from a
23
+ * shared package, mirroring ADR-228's choice (Open Question 3 default —
24
+ * extract to `packages/mcp-atlas-types/` only if a fourth MCP surface
25
+ * lands or the type drift becomes painful).
26
+ *
27
+ * Two shape extensions vs the creator and operator atlases (per ADR-229
28
+ * Decision 4):
29
+ *
30
+ * 1. `clean_empty_state?: { message; suggest_query_shape }` — public-
31
+ * surface analog of `fallback_when_empty`. The directory has no
32
+ * internal action to nudge toward (the empty state IS the final
33
+ * answer for the anonymous user); this field carries the prescribed
34
+ * empty-state copy plus a query-shape hint for the calling LLM.
35
+ *
36
+ * 2. `disambiguation?: { ambiguous_terms[]; resolver_tool; resolver_intent }` —
37
+ * first-class concern on the public surface (creator/operator MCPs
38
+ * have a calling-context that narrows the search; the public
39
+ * directory hits "Bowie", "Sade", "Madonna", "Lover Boy" constantly
40
+ * and must declare an explicit resolver step).
41
+ *
42
+ * `clean_empty_state` and `fallback_when_empty` are mutually exclusive on
43
+ * a single entry — Rule 11F enforces this at lint time.
44
+ *
45
+ * The public atlas omits `natural_domain` (creator-side binds to
46
+ * `discovery.ts` CATEGORIES; the directory MCP has no equivalent
47
+ * taxonomy — the discovery surface is search and lookup, not category-
48
+ * faceted browse). It also omits `available_when` (the operator atlas's
49
+ * forward-compat hook for unshipped sections; the directory has no
50
+ * deferred-section concept).
51
+ */
52
+
53
+ export interface AtlasFallback {
54
+ message: string;
55
+ next_tool: string;
56
+ }
57
+
58
+ export interface AtlasCleanEmptyState {
59
+ /**
60
+ * Atlas-prescribed copy for the empty-state response. The calling LLM
61
+ * may render in its own voice; the suggested-query-shape hint is the
62
+ * load-bearing part.
63
+ */
64
+ message: string;
65
+ /**
66
+ * Free-text hint for how to refine the query, interpreted by the LLM
67
+ * as guidance (not rendered verbatim). Example:
68
+ * `"title + writer | title + year | ISWC if known"`.
69
+ */
70
+ suggest_query_shape: string;
71
+ }
72
+
73
+ export interface AtlasDisambiguation {
74
+ /**
75
+ * Names known to be ambiguous in the music-metadata domain (multiple
76
+ * persons, artist projects, or works with the same string). v1 ships
77
+ * a static list; v2 (deferred per ADR-229 § Risks #3) could resolve
78
+ * dynamically against the index's name-collision frequency.
79
+ */
80
+ ambiguous_terms: string[];
81
+ /**
82
+ * The directory tool the agent should call FIRST to surface candidate
83
+ * entities for the user to disambiguate before committing to the
84
+ * lookup tool in `resolves_to`.
85
+ */
86
+ resolver_tool: string;
87
+ /**
88
+ * Free-text guidance for the LLM on how to run the resolver step.
89
+ * Example: `"Call directory_lookup_person first to surface candidates;
90
+ * ask which one before calling directory_list_works."`
91
+ */
92
+ resolver_intent: string;
93
+ }
94
+
95
+ export type AtlasResolution =
96
+ | { kind: "tool"; name: string; default_args?: Record<string, unknown> }
97
+ | { kind: "prompt"; name: string };
98
+
99
+ export interface AtlasEntry {
100
+ question: string;
101
+ synonyms: string[];
102
+ resolves_to: AtlasResolution;
103
+ /** Mutually exclusive with `clean_empty_state` (lint-enforced). */
104
+ fallback_when_empty?: AtlasFallback;
105
+ /** ADR-229 Decision 4 — public-surface empty-state contract. */
106
+ clean_empty_state?: AtlasCleanEmptyState;
107
+ /** ADR-229 Decision 4 — public-surface name-collision contract. */
108
+ disambiguation?: AtlasDisambiguation;
109
+ }
110
+
111
+ /**
112
+ * Names known-ambiguous in the music-metadata domain — multiple persons
113
+ * sharing a stage name, or artist project names that collide with
114
+ * person names. Seed list per ADR-229 § Open Q2 (case-insensitive — the
115
+ * lint and the resolver normalise on lowercase).
116
+ *
117
+ * Grow this list as Phase 5 eval data surfaces real collisions the
118
+ * static seed misses. Do not prune without eval evidence — the cost of
119
+ * missing a real collision (wrong-tool routing on a public-facing
120
+ * surface) is higher than the cost of an extra resolver call.
121
+ */
122
+ export const AMBIGUOUS_NAMES: string[] = [
123
+ "Bowie",
124
+ "Sade",
125
+ "Madonna",
126
+ "Cher",
127
+ "Drake",
128
+ "Prince",
129
+ "Eminem",
130
+ "Beyoncé",
131
+ "Adele",
132
+ "Sia",
133
+ ];
134
+
135
+ export const PUBLIC_ATLAS: AtlasEntry[] = [
136
+ // ─────────────────────────────────────────────────────────────────
137
+ // A. Identifier-lookup — direct resolution, no disambiguation
138
+ // ─────────────────────────────────────────────────────────────────
139
+ {
140
+ question: "what's the ISWC for X?",
141
+ synonyms: ["ISWC of X", "song code for X", "registration number for X"],
142
+ resolves_to: { kind: "tool", name: "directory_lookup_work" },
143
+ clean_empty_state: {
144
+ message:
145
+ "No work matched that title in our index. The work may not be " +
146
+ "registered with a society we mirror, or the title may be " +
147
+ "ambiguous — try a writer name or year of release to narrow.",
148
+ suggest_query_shape:
149
+ "title + writer | title + year | ISWC if known",
150
+ },
151
+ },
152
+ {
153
+ question: "lookup IPI 12345",
154
+ synonyms: [
155
+ "find IPI X",
156
+ "person with IPI X",
157
+ "who is IPI X?",
158
+ "look up IPI number",
159
+ ],
160
+ resolves_to: { kind: "tool", name: "directory_lookup_person" },
161
+ clean_empty_state: {
162
+ message:
163
+ "No creator matched that IPI in our index. The IPI may not be " +
164
+ "mirrored from the source society yet, or it may belong to a " +
165
+ "publisher rather than a writer — try the directory_list_people " +
166
+ "filter or search by name.",
167
+ suggest_query_shape: "IPI | ISNI | writer name | publisher name",
168
+ },
169
+ },
170
+ {
171
+ question: "lookup ISRC X",
172
+ synonyms: [
173
+ "what's the work for ISRC X?",
174
+ "find the work for this recording",
175
+ "look up ISRC code",
176
+ ],
177
+ resolves_to: { kind: "tool", name: "directory_lookup_isrc" },
178
+ clean_empty_state: {
179
+ message:
180
+ "No work matched that ISRC in our index. The recording may not " +
181
+ "be linked to a registered work yet, or the ISRC may belong to " +
182
+ "a recording outside our mirrored societies — try a title or " +
183
+ "writer-name lookup.",
184
+ suggest_query_shape: "ISRC | title + artist | ISWC if known",
185
+ },
186
+ },
187
+ {
188
+ question: "lookup ISNI X",
189
+ synonyms: [
190
+ "find creator by ISNI",
191
+ "person with ISNI X",
192
+ "look up ISNI identifier",
193
+ ],
194
+ resolves_to: { kind: "tool", name: "directory_lookup_person" },
195
+ clean_empty_state: {
196
+ message:
197
+ "No creator matched that ISNI in our index. The creator may not " +
198
+ "have an ISNI registered, or the ISNI may not be mirrored — try " +
199
+ "an IPI lookup or a name search.",
200
+ suggest_query_shape: "ISNI | IPI | creator name",
201
+ },
202
+ },
203
+ {
204
+ question: "find creator by MusicBrainz ID",
205
+ synonyms: ["lookup MBID X", "person with MusicBrainz ID"],
206
+ resolves_to: { kind: "tool", name: "directory_lookup_person" },
207
+ clean_empty_state: {
208
+ message:
209
+ "No creator matched that MusicBrainz ID in our index. The MBID " +
210
+ "may not be mirrored, or it may identify a release/recording " +
211
+ "rather than a person — try an ISNI or name lookup.",
212
+ suggest_query_shape: "MBID | ISNI | creator name",
213
+ },
214
+ },
215
+
216
+ // ─────────────────────────────────────────────────────────────────
217
+ // B. Name-lookup — works
218
+ // ─────────────────────────────────────────────────────────────────
219
+ {
220
+ question: "who wrote X?",
221
+ synonyms: [
222
+ "who composed X?",
223
+ "writers of X",
224
+ "songwriters for X",
225
+ "credits for song X",
226
+ ],
227
+ resolves_to: { kind: "tool", name: "directory_lookup_work" },
228
+ clean_empty_state: {
229
+ message:
230
+ "No work matched that title. The song may not be in our index " +
231
+ "yet, or the title may be ambiguous (many works share the same " +
232
+ "title) — try a writer name or year of release.",
233
+ suggest_query_shape:
234
+ "title + writer | title + year | ISWC if known",
235
+ },
236
+ },
237
+ {
238
+ question: "find me song X",
239
+ synonyms: ["look up song X by title", "is there a song called X?"],
240
+ resolves_to: { kind: "tool", name: "directory_lookup_work" },
241
+ clean_empty_state: {
242
+ message:
243
+ "No work matched that title in our index. Try refining with the " +
244
+ "writer's name, year of release, or the ISWC if you have it.",
245
+ suggest_query_shape:
246
+ "title + writer | title + year | ISWC if known",
247
+ },
248
+ },
249
+ {
250
+ question: "tell me about song X",
251
+ synonyms: [
252
+ "show me song X",
253
+ "what do you know about song X?",
254
+ "details for song X",
255
+ ],
256
+ resolves_to: { kind: "tool", name: "directory_lookup_work" },
257
+ },
258
+ {
259
+ question: "find recordings of X",
260
+ synonyms: [
261
+ "versions of X",
262
+ "covers of X",
263
+ "who recorded X?",
264
+ "list recordings of song X",
265
+ ],
266
+ // Disambiguation routes through directory_lookup_work first so the
267
+ // agent can pick the right work-row; recordings then come from the
268
+ // work-detail response. NEVER directory_search_recordings — that
269
+ // tool is audio-feature-only despite the verb match.
270
+ disambiguation: {
271
+ ambiguous_terms: AMBIGUOUS_NAMES,
272
+ resolver_tool: "directory_lookup_work",
273
+ resolver_intent:
274
+ "Call directory_lookup_work first to identify the work " +
275
+ "(multiple works may share the title); then read the recordings[] " +
276
+ "field from the work-detail response. If the user typed an " +
277
+ "ISRC-shaped string, call directory_lookup_isrc instead. " +
278
+ "DO NOT call directory_search_recordings — that tool is for " +
279
+ "audio-feature filters (BPM/key/energy), not recording lookup.",
280
+ },
281
+ resolves_to: { kind: "tool", name: "directory_lookup_work" },
282
+ },
283
+
284
+ // ─────────────────────────────────────────────────────────────────
285
+ // C. Name-lookup — people (heavy disambiguation)
286
+ // ─────────────────────────────────────────────────────────────────
287
+ {
288
+ question: "find Bowie's songs",
289
+ synonyms: [
290
+ "songs by X",
291
+ "X discography",
292
+ "X's catalog",
293
+ "what has X written?",
294
+ ],
295
+ disambiguation: {
296
+ ambiguous_terms: AMBIGUOUS_NAMES,
297
+ resolver_tool: "directory_lookup_person",
298
+ resolver_intent:
299
+ "Call directory_lookup_person first to surface candidate persons " +
300
+ "(many stage names map to multiple creators); ask the user to " +
301
+ "pick one before calling directory_list_works with the resolved " +
302
+ "person ID.",
303
+ },
304
+ resolves_to: { kind: "tool", name: "directory_list_works" },
305
+ },
306
+ {
307
+ question: "who is creator X?",
308
+ synonyms: [
309
+ "find creator X",
310
+ "look up artist by name",
311
+ "find the songwriter X",
312
+ ],
313
+ disambiguation: {
314
+ ambiguous_terms: AMBIGUOUS_NAMES,
315
+ resolver_tool: "directory_lookup_person",
316
+ resolver_intent:
317
+ "Call directory_lookup_person; if the response shows multiple " +
318
+ "candidates (common for stage names), surface them and let the " +
319
+ "user pick before any follow-up call.",
320
+ },
321
+ resolves_to: { kind: "tool", name: "directory_lookup_person" },
322
+ },
323
+ {
324
+ question: "tell me about creator X",
325
+ synonyms: [
326
+ "show me creator X",
327
+ "what do you know about creator X?",
328
+ "details for creator X",
329
+ "what's X's catalog?",
330
+ ],
331
+ resolves_to: { kind: "tool", name: "directory_lookup_person" },
332
+ },
333
+ {
334
+ question: "who performs this song?",
335
+ synonyms: [
336
+ "who recorded this version?",
337
+ "performers of this recording",
338
+ "artist on this ISRC",
339
+ ],
340
+ resolves_to: { kind: "tool", name: "directory_lookup_isrc" },
341
+ clean_empty_state: {
342
+ message:
343
+ "No recording matched that identifier. If you have a song title " +
344
+ "instead of an ISRC, look up the work and read its recordings " +
345
+ "list. Performer credits surface there per recording.",
346
+ suggest_query_shape: "ISRC | work title + artist",
347
+ },
348
+ },
349
+
350
+ // ─────────────────────────────────────────────────────────────────
351
+ // D. Discovery-traversal / chain (graph shape)
352
+ // ─────────────────────────────────────────────────────────────────
353
+ {
354
+ question: "show me the rights chain for X",
355
+ synonyms: [
356
+ "full graph for X",
357
+ "rights chain on this song",
358
+ "what's the chain for X?",
359
+ ],
360
+ resolves_to: { kind: "tool", name: "directory_chain" },
361
+ },
362
+ {
363
+ question: "give me everything on this song",
364
+ synonyms: [
365
+ "everything we know about this work",
366
+ "what's the full picture for X?",
367
+ "full picture for X",
368
+ "the whole graph for X",
369
+ ],
370
+ resolves_to: { kind: "tool", name: "directory_chain" },
371
+ },
372
+ {
373
+ question: "trace the chain from this writer to that work",
374
+ synonyms: [
375
+ "how is X connected to Y?",
376
+ "trace this credit",
377
+ "show the connection between X and Y",
378
+ ],
379
+ resolves_to: { kind: "tool", name: "directory_chain" },
380
+ },
381
+
382
+ // ─────────────────────────────────────────────────────────────────
383
+ // E. Browse / filter
384
+ // ─────────────────────────────────────────────────────────────────
385
+ {
386
+ question: "list works in PICA",
387
+ synonyms: [
388
+ "browse the catalog",
389
+ "what works are in the directory?",
390
+ "show me all works",
391
+ ],
392
+ resolves_to: { kind: "tool", name: "directory_list_works" },
393
+ },
394
+ {
395
+ question: "works starting with B",
396
+ synonyms: [
397
+ "list works starting with letter X",
398
+ "browse catalog by first letter",
399
+ "alphabetical work browse",
400
+ ],
401
+ resolves_to: {
402
+ kind: "tool",
403
+ name: "directory_list_works",
404
+ default_args: { letter: "B" },
405
+ },
406
+ },
407
+ {
408
+ question: "find works by publisher X",
409
+ synonyms: [
410
+ "list publisher X's works",
411
+ "what's in publisher X's catalog?",
412
+ "publisher catalog browse",
413
+ ],
414
+ resolves_to: { kind: "tool", name: "directory_list_works" },
415
+ },
416
+ {
417
+ question: "find works by label X",
418
+ synonyms: [
419
+ "list label X's recordings",
420
+ "what's on label X?",
421
+ "label catalog browse",
422
+ ],
423
+ resolves_to: { kind: "tool", name: "directory_list_works" },
424
+ },
425
+ {
426
+ question: "list creators in PICA",
427
+ synonyms: [
428
+ "browse the people directory",
429
+ "what creators are in the directory?",
430
+ "show me all creators",
431
+ ],
432
+ resolves_to: { kind: "tool", name: "directory_list_people" },
433
+ },
434
+ {
435
+ question: "people starting with B",
436
+ synonyms: [
437
+ "list creators starting with letter X",
438
+ "browse creators by first letter",
439
+ "alphabetical people browse",
440
+ ],
441
+ resolves_to: {
442
+ kind: "tool",
443
+ name: "directory_list_people",
444
+ default_args: { letter: "B" },
445
+ },
446
+ },
447
+
448
+ // ─────────────────────────────────────────────────────────────────
449
+ // F. Audio-feature search (sync licensing — distinct from text search)
450
+ // ─────────────────────────────────────────────────────────────────
451
+ {
452
+ question: "find upbeat tracks around 120 BPM",
453
+ synonyms: [
454
+ "find tracks at X BPM",
455
+ "find energetic music for sync",
456
+ "tracks with high energy",
457
+ ],
458
+ resolves_to: { kind: "tool", name: "directory_search_recordings" },
459
+ },
460
+ {
461
+ question: "find instrumental tracks in C minor",
462
+ synonyms: [
463
+ "tracks in [key]",
464
+ "instrumental tracks for sync",
465
+ "find moody music in a minor key",
466
+ ],
467
+ resolves_to: { kind: "tool", name: "directory_search_recordings" },
468
+ },
469
+ {
470
+ question: "tracks at X BPM in [key]",
471
+ synonyms: [
472
+ "find music matching tempo and key",
473
+ "audio-feature search for sync",
474
+ "filter recordings by audio characteristics",
475
+ ],
476
+ resolves_to: { kind: "tool", name: "directory_search_recordings" },
477
+ },
478
+
479
+ // ─────────────────────────────────────────────────────────────────
480
+ // G. Cross-type search (entity type unknown)
481
+ // ─────────────────────────────────────────────────────────────────
482
+ {
483
+ question: "find me X",
484
+ synonyms: [
485
+ "is there anything about X in the directory?",
486
+ "find any record of X",
487
+ "search the directory for X",
488
+ ],
489
+ resolves_to: { kind: "tool", name: "directory_search" },
490
+ clean_empty_state: {
491
+ message:
492
+ "No matches in our public directory. The work, creator, or " +
493
+ "recording may not be mirrored from a society we cover yet, or " +
494
+ "the spelling may differ — try an identifier (ISWC / ISRC / IPI) " +
495
+ "if you have one.",
496
+ suggest_query_shape:
497
+ "name + role | identifier (ISWC/ISRC/IPI/ISNI) | title + writer",
498
+ },
499
+ },
500
+ {
501
+ question: "who or what is X?",
502
+ synonyms: [
503
+ "is X a song or a person?",
504
+ "what kind of thing is X?",
505
+ "fan-out search for X",
506
+ ],
507
+ resolves_to: { kind: "tool", name: "directory_search" },
508
+ clean_empty_state: {
509
+ message:
510
+ "No matches in our public directory across works or creators. " +
511
+ "Try narrowing to one entity type (work title or creator name), " +
512
+ "or supply an identifier if you have one.",
513
+ suggest_query_shape:
514
+ "title + writer | creator name + role | identifier",
515
+ },
516
+ },
517
+
518
+ // ─────────────────────────────────────────────────────────────────
519
+ // H. Registration / status (specialised lookups)
520
+ // ─────────────────────────────────────────────────────────────────
521
+ {
522
+ question: "is this song registered?",
523
+ synonyms: [
524
+ "is X in PICA?",
525
+ "is this work known?",
526
+ "registration status of X",
527
+ "is the ISWC issued for X?",
528
+ ],
529
+ resolves_to: { kind: "tool", name: "directory_lookup_work" },
530
+ clean_empty_state: {
531
+ message:
532
+ "We don't have this work in our directory. That can mean it's " +
533
+ "unregistered, registered with a society we don't mirror, or the " +
534
+ "title is ambiguous. Try a writer name or year to narrow, or " +
535
+ "look up by ISWC if you have one.",
536
+ suggest_query_shape:
537
+ "title + writer | title + year | ISWC if known",
538
+ },
539
+ },
540
+ ];
@@ -0,0 +1,35 @@
1
+ // Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.
2
+
3
+ export const DIRECTORY_PRIMER = `# PICA Directory — Public Music Catalog Search
4
+
5
+ Read-only access to published music catalog data. No authentication required.
6
+ Works and people are only visible if the owning organisation has opted into
7
+ the directory.
8
+
9
+ ## First Connection
10
+
11
+ If you're not sure where to start, invoke the **directory-autopilot** prompt.
12
+ It asks what you need and routes to the right workflow: sync search, rights
13
+ research, or identifier lookup.
14
+
15
+ ## What You Can Do
16
+ - Search works by title, ISWC, publisher, label
17
+ - Search people by name, ISNI, IPI
18
+ - Look up works by recording ISRC (directory_lookup_isrc)
19
+ - Search recordings by audio characteristics (BPM, key, mood, energy)
20
+
21
+ ## What You Cannot Do
22
+ - Create, update, or delete anything (read-only)
23
+ - Access unpublished works or private catalog data
24
+ - See financial data, agreements, or internal metadata
25
+
26
+ ## Entity Model
27
+ - **Work**: composition with title, ISWC, credits, recordings
28
+ - **Person**: writer/composer/performer with IPI, ISNI identifiers
29
+ - **Recording**: audio capture with ISRC, linked to one work
30
+
31
+ ## Recommended Flow
32
+ 1. directory_search (broad text search across works + people)
33
+ 2. directory_lookup_work or directory_lookup_person (detailed view)
34
+ 3. directory_search_recordings (audio characteristic search for sync)
35
+ `;