@withpica/mcp-server-directory 1.1.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 (46) hide show
  1. package/CHANGELOG.md +43 -26
  2. package/dist/prompts/index.d.ts +5 -6
  3. package/dist/prompts/index.d.ts.map +1 -1
  4. package/dist/prompts/index.js +92 -135
  5. package/dist/prompts/index.js.map +1 -1
  6. package/dist/prompts/public-question-atlas.d.ts +121 -0
  7. package/dist/prompts/public-question-atlas.d.ts.map +1 -0
  8. package/dist/prompts/public-question-atlas.js +404 -0
  9. package/dist/prompts/public-question-atlas.js.map +1 -0
  10. package/dist/tools/chain.d.ts +12 -0
  11. package/dist/tools/chain.d.ts.map +1 -0
  12. package/dist/tools/chain.js +109 -0
  13. package/dist/tools/chain.js.map +1 -0
  14. package/dist/tools/index.d.ts +9 -0
  15. package/dist/tools/index.d.ts.map +1 -1
  16. package/dist/tools/index.js +2 -0
  17. package/dist/tools/index.js.map +1 -1
  18. package/dist/tools/people.d.ts +0 -1
  19. package/dist/tools/people.d.ts.map +1 -1
  20. package/dist/tools/people.js +23 -36
  21. package/dist/tools/people.js.map +1 -1
  22. package/dist/tools/recordings.d.ts.map +1 -1
  23. package/dist/tools/recordings.js +7 -3
  24. package/dist/tools/recordings.js.map +1 -1
  25. package/dist/tools/search.d.ts.map +1 -1
  26. package/dist/tools/search.js +7 -4
  27. package/dist/tools/search.js.map +1 -1
  28. package/dist/tools/works.d.ts +0 -1
  29. package/dist/tools/works.d.ts.map +1 -1
  30. package/dist/tools/works.js +41 -42
  31. package/dist/tools/works.js.map +1 -1
  32. package/package.json +3 -2
  33. package/src/__tests__/prompts/index.test.ts +47 -64
  34. package/src/__tests__/prompts/prompt-eval-harness.test.ts +135 -104
  35. package/src/__tests__/tools/chain.test.ts +122 -0
  36. package/src/__tests__/tools/composability-chains.test.ts +4 -2
  37. package/src/__tests__/tools/people.test.ts +9 -3
  38. package/src/__tests__/tools/works.test.ts +32 -3
  39. package/src/prompts/index.ts +97 -141
  40. package/src/prompts/public-question-atlas.ts +540 -0
  41. package/src/tools/chain.ts +118 -0
  42. package/src/tools/index.ts +12 -0
  43. package/src/tools/people.ts +22 -41
  44. package/src/tools/recordings.ts +7 -3
  45. package/src/tools/search.ts +7 -4
  46. package/src/tools/works.ts +39 -46
@@ -24,13 +24,19 @@ describe("DirectoryPeopleTools", () => {
24
24
  jest.clearAllMocks();
25
25
  });
26
26
 
27
- it("registers 3 tools", () => {
27
+ it("registers 2 tools", () => {
28
28
  const tools = peopleTools.getTools();
29
- expect(tools).toHaveLength(3);
29
+ expect(tools).toHaveLength(2);
30
30
  const names = tools.map((t) => t.definition.name);
31
31
  expect(names).toContain("directory_list_people");
32
32
  expect(names).toContain("directory_lookup_person");
33
- expect(names).toContain("directory_lookup_person_full");
33
+ });
34
+
35
+ it("exposes the letter filter on list_people", () => {
36
+ const tool = peopleTools
37
+ .getTools()
38
+ .find((t) => t.definition.name === "directory_list_people")!;
39
+ expect(tool.definition.inputSchema.properties.letter).toBeDefined();
34
40
  });
35
41
 
36
42
  describe("directory_list_people", () => {
@@ -27,16 +27,45 @@ describe("DirectoryWorksTools", () => {
27
27
  jest.clearAllMocks();
28
28
  });
29
29
 
30
- it("registers 4 tools", () => {
30
+ it("registers 3 tools", () => {
31
31
  const tools = worksTools.getTools();
32
- expect(tools).toHaveLength(4);
32
+ expect(tools).toHaveLength(3);
33
33
  const names = tools.map((t) => t.definition.name);
34
34
  expect(names).toContain("directory_list_works");
35
35
  expect(names).toContain("directory_lookup_work");
36
- expect(names).toContain("directory_lookup_work_full");
37
36
  expect(names).toContain("directory_lookup_isrc");
38
37
  });
39
38
 
39
+ it("exposes letter, sort, and isrc filters on list_works", () => {
40
+ const tool = worksTools
41
+ .getTools()
42
+ .find((t) => t.definition.name === "directory_list_works")!;
43
+ expect(tool.definition.inputSchema.properties.letter).toBeDefined();
44
+ expect(tool.definition.inputSchema.properties.sort).toBeDefined();
45
+ expect(tool.definition.inputSchema.properties.isrc).toBeDefined();
46
+ });
47
+
48
+ it("list_works forwards letter, sort, and isrc to /works", async () => {
49
+ mockClient.request.mockResolvedValue({
50
+ success: true,
51
+ data: [],
52
+ pagination: { limit: 20, offset: 0, total: 0 },
53
+ });
54
+
55
+ const tool = worksTools
56
+ .getTools()
57
+ .find((t) => t.definition.name === "directory_list_works")!;
58
+ await tool.executor({ letter: "B", sort: "recent", isrc: "USABC1234567" });
59
+
60
+ expect(mockClient.request).toHaveBeenCalledWith("/works", {
61
+ letter: "B",
62
+ sort: "recent",
63
+ isrc: "USABC1234567",
64
+ limit: "20",
65
+ offset: "0",
66
+ });
67
+ });
68
+
40
69
  describe("directory_list_works", () => {
41
70
  it("calls /works with query params", async () => {
42
71
  mockClient.request.mockResolvedValue({
@@ -1,10 +1,8 @@
1
1
  // Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.
2
2
 
3
3
  /**
4
- * Skill Registry for Directory MCP Server
5
- * Workflow-level skills for music discovery and creator research.
6
- * Deployed as upgraded prompts; will convert to MCP skills format
7
- * when the spec extension ships (ADR-171).
4
+ * Prompt Registry for Directory MCP Server
5
+ * Guided workflows for music discovery, sync licensing, and rights research
8
6
  */
9
7
 
10
8
  export interface PromptDefinition {
@@ -30,29 +28,13 @@ export interface PromptResult {
30
28
  [key: string]: unknown;
31
29
  }
32
30
 
33
- const SHARED_PREAMBLE = `You are guiding a music seeker through the PICA public directory — a discovery layer over verified music catalogs.
34
-
35
- Tone: knowledgeable friend. You know what's available and how to find it. Straightforward, never salesy.
36
-
37
- Discovery principle: every creator's work has equal discoverability. Catalog quality determines visibility, not fame or follower count. Results are sorted by musical fit, not popularity. An unknown songwriter with analysed audio ranks the same as a major artist.
38
-
39
- Before starting this workflow:
40
- 1. Read the llms://primer resource to understand what's available in the directory
41
- 2. This is a read-only public directory — you can search and look up, but you can't create or modify anything
42
- 3. Only works from organisations that opted into the directory are visible
43
-
44
- After completing this workflow:
45
- - Summarise what you found in one short paragraph
46
- - Suggest the natural next step
47
- - If they say no, that's fine. Don't ask twice.`;
48
-
49
31
  export class PromptRegistry {
50
32
  listPrompts(): PromptDefinition[] {
51
33
  return [
52
34
  {
53
- name: "discover-music",
35
+ name: "find-music",
54
36
  description:
55
- "Find music for a sync brief, playlist, or project — search by mood, BPM, key, energy, or description. Equal discovery: results sorted by musical fit, not popularity",
37
+ "Find music for a sync brief, playlist, or project — search by mood, BPM, key, energy, or description",
56
38
  arguments: [
57
39
  {
58
40
  name: "brief",
@@ -65,7 +47,7 @@ export class PromptRegistry {
65
47
  {
66
48
  name: "research-creator",
67
49
  description:
68
- "Research a songwriter, composer, or performer — full catalog, identifiers, collaborator network, rights landscape, and similarity search",
50
+ "Research a songwriter, composer, or performer — see their works, identifiers, collaborators, and verification status",
69
51
  arguments: [
70
52
  {
71
53
  name: "name_or_id",
@@ -74,6 +56,12 @@ export class PromptRegistry {
74
56
  },
75
57
  ],
76
58
  },
59
+ {
60
+ name: "directory-autopilot",
61
+ description:
62
+ "Not sure where to start? Describe what you need and get routed to the right workflow — sync search, rights research, or identifier lookup",
63
+ arguments: [],
64
+ },
77
65
  ];
78
66
  }
79
67
 
@@ -82,19 +70,21 @@ export class PromptRegistry {
82
70
  args?: Record<string, any>,
83
71
  ): Promise<PromptResult> {
84
72
  switch (name) {
85
- case "discover-music":
86
- return this.getDiscoverMusicSkill(args?.brief);
73
+ case "find-music":
74
+ return this.getFindMusicPrompt(args?.brief);
87
75
  case "research-creator":
88
- return this.getResearchCreatorSkill(args?.name_or_id);
76
+ return this.getResearchCreatorPrompt(args?.name_or_id);
77
+ case "directory-autopilot":
78
+ return this.getDirectoryAutopilotPrompt();
89
79
  default:
90
80
  throw new Error(`Prompt not found: ${name}`);
91
81
  }
92
82
  }
93
83
 
94
- private getDiscoverMusicSkill(brief?: string): PromptResult {
84
+ private getFindMusicPrompt(brief?: string): PromptResult {
95
85
  const briefInstruction = brief
96
86
  ? `The user is looking for: "${brief}". Translate this into audio search parameters.`
97
- : `Ask what they're looking for — a mood, a scene, a vibe, or specific audio characteristics (BPM, key, energy).`;
87
+ : `Ask me what I'm looking for — a mood, a scene, a vibe, or specific audio characteristics (BPM, key, energy).`;
98
88
 
99
89
  return {
100
90
  messages: [
@@ -102,80 +92,44 @@ export class PromptRegistry {
102
92
  role: "user",
103
93
  content: {
104
94
  type: "text",
105
- text: `${SHARED_PREAMBLE}
106
-
107
- --- SKILL: discover-music ---
108
-
109
- Help find music by what it sounds like, not who made it. Equal discovery — every well-catalogued work has the same chance.
95
+ text: `Help me find music in the PICA public directory for a sync brief or project.
110
96
 
111
97
  ${briefInstruction}
112
98
 
113
- Sync brief / mood-based search:
114
- Extract parameters from natural language:
115
- mood, BPM range, key, energy, instrumentation, vocal/instrumental, duration constraints
116
- Translate mood descriptions to search parameters for directory_search_recordings:
117
- - "Upbeat" → min_energy: 0.6, min_danceability: 0.5
118
- - "Dark/moody" → max_energy: 0.4, key_mode: minor
119
- - "Chill"max_energy: 0.4, max_bpm: 100
120
- - Specific requests → use BPM, key, duration filters directly
121
- → Run directory_search_recordings with the parameters
122
- → Present results by musical fit, not popularity:
123
- "here are [N] tracks that match. sorted by how closely they fit your brief, not by who made them."
124
- → Per result: title, creator, BPM, key, mood tags, duration.
125
- No follower counts. No stream numbers.
126
-
127
- For each promising result, use directory_lookup_work_full to get:
128
- - Who wrote it (credits with IPI numbers)
129
- - Whether credits are attested (verified ownership)
130
- - DSP links (Spotify, Apple Music — so the user can listen)
131
- - Registration score (higher = cleaner rights chain)
132
-
133
- Present as a shortlist with:
134
- - Title, artist, BPM, key, energy
135
- - Credits summary (who to contact for licensing)
136
- - Any flags (unattested credits, low registration score, missing ISRC)
137
-
138
- Reference track search:
139
- → "got a reference track? tell me what you like about it."
140
- → Parse what they value: tempo, mood, instrumentation, vocal style
141
- → Search by those parameters
142
-
143
- Similarity exploration:
144
- → "want to find music similar to [track] by someone you've never heard of?"
145
- → Search by the sound, not the name — this is where the discovery principle lives
146
-
147
- If they like one:
148
- → "want to hear more from this creator, or find similar tracks?"
149
- → Similar → search with same parameters
150
- → More from creator → pivot to research-creator skill
151
-
152
- Rights check:
153
- → For any track: "want to check the rights situation?"
154
- → Show ownership, publisher, contact path via directory_lookup_work_full
155
- → If enquiry → guide through submission
99
+ Workflow:
100
+ 1. Translate the brief into search parameters for directory_search_recordings:
101
+ - Mood descriptions energy, danceability, key_mode, mood filters
102
+ - "Upbeat" min_energy: 0.6, min_danceability: 0.5
103
+ - "Dark/moody" → max_energy: 0.4, key_mode: minor
104
+ - "Chill" → max_energy: 0.4, max_bpm: 100
105
+ - Specific requests use BPM, key, duration filters directly
156
106
 
157
- If no results match, suggest broadening the search (wider BPM range, drop key filter, etc.).
107
+ 2. Run directory_search_recordings with the parameters.
108
+
109
+ 3. For each promising result, use directory_lookup_work to get:
110
+ - Who wrote it (credits with IPI numbers)
111
+ - Whether credits are attested (verified ownership)
112
+ - DSP links (Spotify, Apple Music — so the user can listen)
113
+ - Registration score (higher = cleaner rights chain)
158
114
 
159
- If the catalog is small, say so: "the directory currently has [N] works — it's growing. here's what matches from what's available."
115
+ 4. Present results as a shortlist with:
116
+ - Title, artist, BPM, key, energy
117
+ - Credits summary (who to contact for licensing)
118
+ - Any flags (unattested credits, low registration score, missing ISRC)
160
119
 
161
- Domain knowledge:
162
- - Results sorted by audio-parameter match, not streams or followers
163
- - Reference track searching works by extracting musical qualities, not artist similarity
164
- - Rights information is always available — the directory doesn't hide who owns what
165
- - Sync supervisors think in briefs (mood + tempo + energy + duration), not artist names
166
- - Similarity search by audio surfaces genuinely similar music the seeker might never have found
120
+ If no results match, suggest broadening the search (wider BPM range, drop key filter, etc.).
167
121
 
168
- Tools: directory_search_recordings, directory_search, directory_lookup_work, directory_lookup_work_full, directory_lookup_person, directory_lookup_person_full`,
122
+ Important: This is a public directory — only works from organisations that opted in are visible. If the catalog is small, say so.`,
169
123
  },
170
124
  },
171
125
  ],
172
126
  };
173
127
  }
174
128
 
175
- private getResearchCreatorSkill(nameOrId?: string): PromptResult {
129
+ private getResearchCreatorPrompt(nameOrId?: string): PromptResult {
176
130
  const searchInstruction = nameOrId
177
- ? `Look up: "${nameOrId}". Try directory_lookup_person_full first (it accepts name, IPI, ISNI, or MusicBrainz ID). If that doesn't match, use directory_list_people with a text search.`
178
- : `Ask for the creator's name, IPI number, ISNI, or MusicBrainz ID.`;
131
+ ? `Look up: "${nameOrId}". Try directory_lookup_person first (it accepts name, IPI, ISNI, or MusicBrainz ID). If that doesn't match, use directory_list_people with a text search.`
132
+ : `Ask me for the creator's name, IPI number, ISNI, or MusicBrainz ID.`;
179
133
 
180
134
  return {
181
135
  messages: [
@@ -183,65 +137,67 @@ Tools: directory_search_recordings, directory_search, directory_lookup_work, dir
183
137
  role: "user",
184
138
  content: {
185
139
  type: "text",
186
- text: `${SHARED_PREAMBLE}
140
+ text: `Help me research a music creator in the PICA public directory.
187
141
 
188
- --- SKILL: research-creator ---
142
+ ${searchInstruction}
189
143
 
190
- Deep-dive on a specific creator. Full catalog, identifiers, collaborator network, rights landscape.
144
+ Once you find the person, use directory_lookup_person to get their complete profile:
145
+ - Identifiers (IPI, ISNI, MusicBrainz) — essential for rights verification
146
+ - All credited works with roles (writer, composer, performer, producer)
147
+ - Collaborator network — who they work with
148
+ - Verification score — how complete their profile is
149
+
150
+ Then for their most notable works (up to 5), use directory_lookup_work to get:
151
+ - Full credits and splits (are they the sole writer or one of many?)
152
+ - Recordings with ISRCs
153
+ - Audio analysis (BPM, key, energy)
154
+ - DSP links and registration status
155
+
156
+ Present a summary:
157
+ - Creator profile (name, identifiers, role patterns)
158
+ - Catalog overview (how many works, typical roles, common collaborators)
159
+ - Notable works with credit details
160
+ - Any gaps (missing identifiers, unattested credits, low scores)
161
+
162
+ This is useful for: rights research, due diligence before licensing, publisher evaluation, or understanding a catalog before acquisition.`,
163
+ },
164
+ },
165
+ ],
166
+ };
167
+ }
191
168
 
192
- Also handles routing that was previously in directory-autopilot:
193
- - If the user has a specific identifier to resolve (ISRC, ISWC, IPI, ISNI):
194
- ISRC (e.g. USABC1234567) → directory_lookup_isrc → then directory_lookup_work_full
195
- → ISWC (e.g. T-123.456.789-0) → directory_lookup_work_full
196
- IPI or ISNI → directory_lookup_person_full
197
- MusicBrainz ID → directory_lookup_person_full
198
- - If the user wants to browse → use directory_search with a broad query, or directory_list_works / directory_list_people
169
+ private getDirectoryAutopilotPrompt(): PromptResult {
170
+ return {
171
+ messages: [
172
+ {
173
+ role: "user",
174
+ content: {
175
+ type: "text",
176
+ text: `You've connected to the PICA public music directory. Help me find what I need.
199
177
 
200
- "who do you want to learn about?"
178
+ First, read the llms://primer resource to understand what's available.
201
179
 
202
- ${searchInstruction}
180
+ Then ask me what I'm looking for. Based on my answer, route to the right workflow:
181
+
182
+ If I'm looking for MUSIC FOR A PROJECT (sync brief, playlist, mood search):
183
+ → Use the find-music workflow. Translate my description into audio search parameters and search recordings by BPM, key, energy, mood. Then look up full details on promising matches.
184
+
185
+ If I'm looking for INFORMATION ABOUT A CREATOR (songwriter, composer, performer):
186
+ → Use the research-creator workflow. Look up their profile, works, identifiers, and collaborators.
187
+
188
+ If I have a SPECIFIC IDENTIFIER to resolve (ISRC, ISWC, IPI, ISNI):
189
+ → Route directly to the right lookup tool:
190
+ - ISRC (e.g. USABC1234567) → directory_lookup_isrc → then directory_lookup_work
191
+ - ISWC (e.g. T-123.456.789-0) → directory_lookup_work
192
+ - IPI or ISNI → directory_lookup_person
193
+ - MusicBrainz ID → directory_lookup_person
194
+
195
+ If I want to BROWSE what's in the directory:
196
+ → Use directory_search with a broad query, or directory_list_works / directory_list_people to paginate.
197
+
198
+ Tell me which workflow you chose and why, in one sentence. Offer alternatives.
203
199
 
204
- Lookup by name, IPI, ISNI, or ISRC:
205
- → Find the person using directory_lookup_person_full
206
- → If multiple matches: present with distinguishing works/roles
207
-
208
- Creator profile:
209
- → Full catalog: all works, roles, co-writers
210
- → Identifiers: IPI, ISNI, MusicBrainz, IPN
211
- → Collaborator network: "frequently works with [names]"
212
- → Genre/mood footprint: based on analysed audio across catalog
213
-
214
- For their most notable works (up to 5), use directory_lookup_work_full to get:
215
- - Full credits and splits (are they the sole writer or one of many?)
216
- - Recordings with ISRCs
217
- - Audio analysis (BPM, key, energy)
218
- - DSP links and registration status
219
-
220
- Cross-reference:
221
- → "want to verify their identifiers against ISNI or MusicBrainz?"
222
- → Flag discrepancies
223
-
224
- Similarity exploration:
225
- → "want to find other creators with a similar sound?"
226
- → Use directory_search_recordings by audio characteristics of their catalog
227
- → Equal discovery: results by sound, not fame
228
-
229
- Rights landscape:
230
- → "want to know who controls their catalog?"
231
- → Publishers, agreements, licensing paths
232
-
233
- After research:
234
- → Summary: creator profile, catalog overview, notable works, gaps
235
- → "want to submit a licensing enquiry, or explore more creators?"
236
-
237
- Domain knowledge:
238
- - Creator research often starts from one work and expands via collaborator network — adjacent talent discovery
239
- - Identifier verification across sources catches discrepancies that matter for rights
240
- - Similarity search by audio surfaces genuinely similar music through non-traditional channels
241
- - The collaborator network is how you discover creators no algorithm would surface
242
- - This is useful for: rights research, due diligence before licensing, publisher evaluation, or catalog acquisition
243
-
244
- Tools: directory_lookup_person_full, directory_lookup_work_full, directory_search_recordings, directory_search, directory_lookup_isrc, directory_list_works, directory_list_people`,
200
+ Important: This is a read-only public directory. I can search and look up, but I can't create or modify anything. Only works from organisations that opted into the directory are visible.`,
245
201
  },
246
202
  },
247
203
  ],