@withpica/mcp-server-directory 1.2.0 → 1.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.
- package/package.json +2 -1
- package/scripts/build-skills.ts +192 -0
- package/src/__tests__/skills/skills-registry.test.ts +63 -0
- package/src/__tests__/skills/skills-tools.test.ts +74 -0
- package/src/prompts/public-question-atlas.ts +17 -0
- package/src/server.ts +123 -11
- package/src/skills/find-music-for-sync-brief/SKILL.md +89 -0
- package/src/skills/index.ts +63 -0
- package/src/skills/skills.generated.ts +33 -0
- package/src/tools/index.ts +3 -0
- package/src/tools/skills.ts +146 -0
- package/dist/client.d.ts +0 -12
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -93
- package/dist/client.js.map +0 -1
- package/dist/config.d.ts +0 -9
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -15
- package/dist/config.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -28
- package/dist/index.js.map +0 -1
- package/dist/prompts/index.d.ts +0 -32
- package/dist/prompts/index.d.ts.map +0 -1
- package/dist/prompts/index.js +0 -165
- package/dist/prompts/index.js.map +0 -1
- package/dist/prompts/public-question-atlas.d.ts +0 -121
- package/dist/prompts/public-question-atlas.d.ts.map +0 -1
- package/dist/prompts/public-question-atlas.js +0 -404
- package/dist/prompts/public-question-atlas.js.map +0 -1
- package/dist/resources/llms-primer.d.ts +0 -2
- package/dist/resources/llms-primer.d.ts.map +0 -1
- package/dist/resources/llms-primer.js +0 -35
- package/dist/resources/llms-primer.js.map +0 -1
- package/dist/server.d.ts +0 -13
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js +0 -104
- package/dist/server.js.map +0 -1
- package/dist/tools/chain.d.ts +0 -12
- package/dist/tools/chain.d.ts.map +0 -1
- package/dist/tools/chain.js +0 -109
- package/dist/tools/chain.js.map +0 -1
- package/dist/tools/index.d.ts +0 -35
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js +0 -45
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/people.d.ts +0 -13
- package/dist/tools/people.d.ts.map +0 -1
- package/dist/tools/people.js +0 -177
- package/dist/tools/people.js.map +0 -1
- package/dist/tools/recordings.d.ts +0 -12
- package/dist/tools/recordings.d.ts.map +0 -1
- package/dist/tools/recordings.js +0 -145
- package/dist/tools/recordings.js.map +0 -1
- package/dist/tools/search.d.ts +0 -12
- package/dist/tools/search.d.ts.map +0 -1
- package/dist/tools/search.js +0 -56
- package/dist/tools/search.js.map +0 -1
- package/dist/tools/works.d.ts +0 -14
- package/dist/tools/works.d.ts.map +0 -1
- package/dist/tools/works.js +0 -248
- package/dist/tools/works.js.map +0 -1
- package/dist/utils/errors.d.ts +0 -14
- package/dist/utils/errors.d.ts.map +0 -1
- package/dist/utils/errors.js +0 -48
- package/dist/utils/errors.js.map +0 -1
- package/dist/utils/formatting.d.ts +0 -10
- package/dist/utils/formatting.d.ts.map +0 -1
- package/dist/utils/formatting.js +0 -16
- package/dist/utils/formatting.js.map +0 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Skills Registry — directory MCP, ADR-140 Phase 2b
|
|
5
|
+
*
|
|
6
|
+
* Sister of mcp-server/src/skills/index.ts. Same shape, scoped to the public
|
|
7
|
+
* directory MCP. Body bytes are generated at build time by
|
|
8
|
+
* `scripts/build-skills.ts` and inlined into `skills.generated.ts`.
|
|
9
|
+
*
|
|
10
|
+
* Public/anonymous surface — every skill body must clear the IP-protection
|
|
11
|
+
* lint (scripts/lint-mcp-tools.ts Rule 14) before publishing. Directory MCP
|
|
12
|
+
* skills MUST NOT quote from trade-secret directories listed in
|
|
13
|
+
* `.claude/rules/ip-protection.md`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { SKILLS, SKILL_NAMES, type Skill } from "./skills.generated.js";
|
|
17
|
+
|
|
18
|
+
export interface SkillSummary {
|
|
19
|
+
name: string;
|
|
20
|
+
description: string;
|
|
21
|
+
triggers: string[];
|
|
22
|
+
audience: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class SkillsRegistry {
|
|
26
|
+
listNames(): ReadonlyArray<string> {
|
|
27
|
+
return SKILL_NAMES;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
listSkills(): SkillSummary[] {
|
|
31
|
+
return SKILL_NAMES.map((name) => {
|
|
32
|
+
const skill = SKILLS[name];
|
|
33
|
+
return {
|
|
34
|
+
name: skill.name,
|
|
35
|
+
description: skill.description,
|
|
36
|
+
triggers: skill.triggers,
|
|
37
|
+
audience: skill.audience,
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getSkill(name: string): Skill {
|
|
43
|
+
const skill = SKILLS[name];
|
|
44
|
+
if (!skill) {
|
|
45
|
+
throw new SkillNotFoundError(name);
|
|
46
|
+
}
|
|
47
|
+
return skill;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
has(name: string): boolean {
|
|
51
|
+
return name in SKILLS;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class SkillNotFoundError extends Error {
|
|
56
|
+
constructor(public readonly skillName: string) {
|
|
57
|
+
super(`Skill not found: ${skillName}`);
|
|
58
|
+
this.name = "SkillNotFoundError";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { SKILLS, SKILL_NAMES };
|
|
63
|
+
export type { Skill };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GENERATED FILE — DO NOT EDIT BY HAND.
|
|
5
|
+
*
|
|
6
|
+
* Regenerated by `scripts/build-skills.ts` from `src/skills/<name>/SKILL.md`.
|
|
7
|
+
* Runs in `prebuild`. See ADR-140 Phase 2b.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface Skill {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
triggers: string[];
|
|
14
|
+
audience: string;
|
|
15
|
+
tools_required: string[];
|
|
16
|
+
output: string;
|
|
17
|
+
body: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
export const SKILLS: Record<string, Skill> = {
|
|
22
|
+
"find-music-for-sync-brief": {
|
|
23
|
+
name: "find-music-for-sync-brief",
|
|
24
|
+
description: "Find music in the PICA public directory for a sync brief — translate a mood/vibe/scene description into audio search parameters, run the search, and return a rights-aware shortlist with credit details for licensing.",
|
|
25
|
+
triggers: ["find music for a sync brief","find me a track for a film scene","music for this brief","find an uplifting indie folk track","I need music that sounds like"],
|
|
26
|
+
audience: "sync supervisor / music library / ad agency / film & TV music team",
|
|
27
|
+
tools_required: ["directory_search_recordings","directory_lookup_work","directory_lookup_person"],
|
|
28
|
+
output: "A rights-aware shortlist — each result with title, artist, audio characteristics (BPM, key, energy), credits summary for licensing contact, and any flags (unattested credits, low registration score, missing ISRC).",
|
|
29
|
+
body: "# find-music-for-sync-brief\n\nHelp the user find music in PICA's public directory for a sync brief. Translate their description (mood, vibe, scene, era, vocal style) into search parameters, run the search, and return a shortlist that includes the information they need to license — not just the track.\n\nThis is a public skill — any AI agent connected to the directory MCP can use it. The directory only contains works from organisations that have opted in.\n\n## Step 1 — Translate the brief\n\nMost sync briefs are described in natural language. Translate before searching:\n\n| User says | Search parameters |\n|---|---|\n| upbeat / uplifting / driving | `min_energy: 0.6`, `min_danceability: 0.5` |\n| dark / moody / brooding | `max_energy: 0.4`, `key_mode: minor` |\n| chill / ambient / background | `max_energy: 0.4`, `max_bpm: 100` |\n| epic / cinematic | `min_energy: 0.7`, often longer durations |\n| specific BPM (\"around 120bpm\") | `min_bpm: 115`, `max_bpm: 125` |\n| acoustic / sparse | typically lower `danceability` and `energy` |\n| vocal / instrumental | filter on `has_vocals` if available |\n\nIf the user gives an artist or song reference (\"something like X\"), look up that reference first via `directory_lookup_work` or `directory_search_recordings` by title — then mimic its audio profile in the new search.\n\n## Step 2 — Search\n\nCall `directory_search_recordings` with the parameters from step 1. Default limit is fine for an initial scan.\n\nIf the brief is vague, do two searches with slightly different parameters and compare. If too many results, narrow on BPM or key. If too few, widen the BPM range first, then drop key_mode.\n\n## Step 3 — Lookup full details\n\nFor the top 5-10 promising results, call `directory_lookup_work` to get:\n\n- **Who wrote it** — credits with IPI numbers (essential for the licensing contact path)\n- **Whether credits are attested** — an unattested credit means the ownership claim hasn't been verified; flag this\n- **DSP links** (Spotify, Apple Music) — the user wants to listen before shortlisting\n- **Registration score** — higher score = cleaner rights chain = easier to license\n\nFor high-value matches where the user wants to know more about the creator, call `directory_lookup_person` with the IPI to see the writer's broader catalog and collaborator network.\n\n## Step 4 — Present the shortlist\n\nPresent each shortlisted track with:\n\n- **Title, artist** — what the user listens for\n- **Audio profile** — BPM, key, energy (one line, the supervisor's quick filter)\n- **Credits summary** — writers with IPI, publisher if known (this is the licensing path)\n- **DSP links** — so the user can listen\n- **Flags** — unattested credits, low registration score, missing ISRC, multiple writers (= multi-party clearance needed)\n\nIf a track has all green flags (attested credits, high registration score, ISRC present, single or low-count writers), call it out as \"clean to clear.\"\n\n## Step 5 — If no results\n\nIf the search returns nothing, walk the user back through the brief:\n- Widen the BPM range first (most supervisors don't actually mind ±10 BPM)\n- Drop the key filter (key matching matters less than mood for most placements)\n- Drop the energy bounds and search by genre/mood text only\n- Suggest the user expand to adjacent moods (\"uplifting\" → also try \"warm\" or \"hopeful\")\n\nBe honest if the directory is small for a specific style — PICA's directory grows with org opt-ins; some niches are sparse.\n\n## What not to do\n\n- Don't claim to license tracks — this skill returns a shortlist, not a license. The user contacts the rights holder via the credits summary.\n- Don't speculate about pricing — sync fees are negotiated per-placement\n- Don't return results from outside the directory (no general web search, no Spotify metadata directly) — the directory is the source of truth for this skill\n- Don't return tracks with no attested credits without flagging them as \"rights chain unverified\"\n\n## Follow-on skills\n\n- `due-diligence-on-creator` (Stage 2 candidate) — for deeper research on a specific writer before reaching out\n",
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const SKILL_NAMES: ReadonlyArray<string> = ["find-music-for-sync-brief"];
|
package/src/tools/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { DirectoryPeopleTools } from "./people.js";
|
|
|
6
6
|
import { DirectorySearchTools } from "./search.js";
|
|
7
7
|
import { DirectoryRecordingsTools } from "./recordings.js";
|
|
8
8
|
import { DirectoryChainTools } from "./chain.js";
|
|
9
|
+
import { DirectorySkillsTools } from "./skills.js";
|
|
9
10
|
import { formatError, logError, ToolExecutionError } from "../utils/errors.js";
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -25,6 +26,7 @@ export interface ToolDefinition {
|
|
|
25
26
|
type: string;
|
|
26
27
|
properties: Record<string, any>;
|
|
27
28
|
required?: string[];
|
|
29
|
+
additionalProperties?: boolean;
|
|
28
30
|
};
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -51,6 +53,7 @@ export class ToolRegistry {
|
|
|
51
53
|
new DirectorySearchTools(client),
|
|
52
54
|
new DirectoryRecordingsTools(client),
|
|
53
55
|
new DirectoryChainTools(client),
|
|
56
|
+
new DirectorySkillsTools(),
|
|
54
57
|
];
|
|
55
58
|
|
|
56
59
|
for (const group of groups) {
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Directory MCP skill tools — ADR-140 Phase 2b
|
|
5
|
+
*
|
|
6
|
+
* Sister of mcp-server/src/tools/skills.ts. Two meta-tools — list and get —
|
|
7
|
+
* named `directory_skill_*` to match the directory MCP's `directory_*` prefix
|
|
8
|
+
* convention. Same lazy-loading shape as the creator MCP variant.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { ToolDefinition, ToolExecutor, ToolResult } from "./index.js";
|
|
12
|
+
import { SkillsRegistry, SkillNotFoundError } from "../skills/index.js";
|
|
13
|
+
|
|
14
|
+
export class DirectorySkillsTools {
|
|
15
|
+
private registry: SkillsRegistry;
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
this.registry = new SkillsRegistry();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getTools(): Array<{ definition: ToolDefinition; executor: ToolExecutor }> {
|
|
22
|
+
return [
|
|
23
|
+
{
|
|
24
|
+
definition: {
|
|
25
|
+
name: "directory_skill_list",
|
|
26
|
+
tier: "read",
|
|
27
|
+
description:
|
|
28
|
+
"Use when the user asks: 'what skills do you have?', 'what can you do?', 'show me your methodologies', 'how do I find music here?'. " +
|
|
29
|
+
"Returns the list of downloadable directory skill methodologies — name, description, trigger phrases. " +
|
|
30
|
+
"Cheap to call. Use directory_skill_get(name) to load a specific skill's full methodology body.",
|
|
31
|
+
inputSchema: {
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {},
|
|
34
|
+
additionalProperties: false,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
executor: this.listSkills.bind(this),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
definition: {
|
|
41
|
+
name: "directory_skill_get",
|
|
42
|
+
tier: "read",
|
|
43
|
+
description:
|
|
44
|
+
"Use when the user asks: 'what skills do you have?', 'what can you do?', 'show me your methodologies'. " +
|
|
45
|
+
"Returns the full methodology body for one directory skill — markdown with step-by-step instructions and tool chains. " +
|
|
46
|
+
"Call directory_skill_list first to see available skill names, then directory_skill_get to load one. Read once at the start of the workflow; do not re-fetch on every step.",
|
|
47
|
+
inputSchema: {
|
|
48
|
+
type: "object",
|
|
49
|
+
properties: {
|
|
50
|
+
name: {
|
|
51
|
+
type: "string",
|
|
52
|
+
description:
|
|
53
|
+
"Kebab-case skill name from directory_skill_list (e.g. 'find-music-for-sync-brief').",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
required: ["name"],
|
|
57
|
+
additionalProperties: false,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
executor: this.getSkill.bind(this),
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private async listSkills(_args: Record<string, any>): Promise<ToolResult> {
|
|
66
|
+
const skills = this.registry.listSkills();
|
|
67
|
+
return {
|
|
68
|
+
content: [
|
|
69
|
+
{
|
|
70
|
+
type: "text",
|
|
71
|
+
text: skills.length
|
|
72
|
+
? `Available directory skills (${skills.length}):\n\n` +
|
|
73
|
+
skills
|
|
74
|
+
.map(
|
|
75
|
+
(s) =>
|
|
76
|
+
`- ${s.name} — ${s.description}\n triggers: ${s.triggers.join(", ")}\n audience: ${s.audience}`,
|
|
77
|
+
)
|
|
78
|
+
.join("\n\n") +
|
|
79
|
+
"\n\nCall directory_skill_get(name) to load a skill's full methodology."
|
|
80
|
+
: "No directory skills available.",
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
structuredContent: { skills },
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private async getSkill(args: Record<string, any>): Promise<ToolResult> {
|
|
88
|
+
const name = String(args?.name ?? "").trim();
|
|
89
|
+
if (!name) {
|
|
90
|
+
return {
|
|
91
|
+
isError: true,
|
|
92
|
+
content: [
|
|
93
|
+
{
|
|
94
|
+
type: "text",
|
|
95
|
+
text: "Missing required argument: name. Call directory_skill_list to see available skill names.",
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
let skill;
|
|
101
|
+
try {
|
|
102
|
+
skill = this.registry.getSkill(name);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
if (err instanceof SkillNotFoundError) {
|
|
105
|
+
return {
|
|
106
|
+
isError: true,
|
|
107
|
+
content: [
|
|
108
|
+
{
|
|
109
|
+
type: "text",
|
|
110
|
+
text: `Skill not found: ${name}. Call directory_skill_list to see available skill names.`,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
throw err;
|
|
116
|
+
}
|
|
117
|
+
const frontmatterSummary = [
|
|
118
|
+
`# ${skill.name}`,
|
|
119
|
+
"",
|
|
120
|
+
`**Description:** ${skill.description}`,
|
|
121
|
+
`**Audience:** ${skill.audience}`,
|
|
122
|
+
`**Triggers:** ${skill.triggers.join(", ")}`,
|
|
123
|
+
`**Tools used:** ${skill.tools_required.join(", ")}`,
|
|
124
|
+
`**Output:** ${skill.output}`,
|
|
125
|
+
"",
|
|
126
|
+
"---",
|
|
127
|
+
"",
|
|
128
|
+
].join("\n");
|
|
129
|
+
return {
|
|
130
|
+
content: [
|
|
131
|
+
{
|
|
132
|
+
type: "text",
|
|
133
|
+
text: frontmatterSummary + skill.body,
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
structuredContent: {
|
|
137
|
+
name: skill.name,
|
|
138
|
+
description: skill.description,
|
|
139
|
+
triggers: skill.triggers,
|
|
140
|
+
audience: skill.audience,
|
|
141
|
+
tools_required: skill.tools_required,
|
|
142
|
+
output: skill.output,
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
package/dist/client.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export interface DirectoryClientConfig {
|
|
2
|
-
baseUrl: string;
|
|
3
|
-
debug?: boolean;
|
|
4
|
-
}
|
|
5
|
-
export declare class DirectoryClient {
|
|
6
|
-
private baseUrl;
|
|
7
|
-
private debug;
|
|
8
|
-
constructor(config: DirectoryClientConfig);
|
|
9
|
-
private fetchWithTimeout;
|
|
10
|
-
request<T>(path: string, params?: Record<string, string>): Promise<T>;
|
|
11
|
-
}
|
|
12
|
-
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,KAAK,CAAU;gBAEX,MAAM,EAAE,qBAAqB;YAK3B,gBAAgB;IAmBxB,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAgF5E"}
|
package/dist/client.js
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.
|
|
2
|
-
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
3
|
-
const MAX_RETRIES = 2;
|
|
4
|
-
const RETRY_BASE_MS = 500;
|
|
5
|
-
function isRetryableStatus(status) {
|
|
6
|
-
return status === 429 || status === 502 || status === 503 || status === 504;
|
|
7
|
-
}
|
|
8
|
-
function sleep(ms) {
|
|
9
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10
|
-
}
|
|
11
|
-
export class DirectoryClient {
|
|
12
|
-
baseUrl;
|
|
13
|
-
debug;
|
|
14
|
-
constructor(config) {
|
|
15
|
-
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
16
|
-
this.debug = config.debug || false;
|
|
17
|
-
}
|
|
18
|
-
async fetchWithTimeout(url, init, timeoutMs) {
|
|
19
|
-
const controller = new AbortController();
|
|
20
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
21
|
-
try {
|
|
22
|
-
return await fetch(url, { ...init, signal: controller.signal });
|
|
23
|
-
}
|
|
24
|
-
catch (err) {
|
|
25
|
-
if (err.name === "AbortError") {
|
|
26
|
-
throw new Error(`Request timed out after ${timeoutMs}ms: GET ${url}`);
|
|
27
|
-
}
|
|
28
|
-
throw err;
|
|
29
|
-
}
|
|
30
|
-
finally {
|
|
31
|
-
clearTimeout(timer);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
async request(path, params) {
|
|
35
|
-
const url = new URL(`${this.baseUrl}/api/public/directory${path}`);
|
|
36
|
-
if (params) {
|
|
37
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
38
|
-
if (value !== undefined && value !== "") {
|
|
39
|
-
url.searchParams.set(key, value);
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
if (this.debug) {
|
|
44
|
-
console.error(`[Directory MCP] GET ${url.toString()} (timeout: ${DEFAULT_TIMEOUT_MS}ms)`);
|
|
45
|
-
}
|
|
46
|
-
let lastError;
|
|
47
|
-
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
48
|
-
try {
|
|
49
|
-
const response = await this.fetchWithTimeout(url.toString(), { method: "GET", headers: { Accept: "application/json" } }, DEFAULT_TIMEOUT_MS);
|
|
50
|
-
if (response.ok) {
|
|
51
|
-
const json = await response.json();
|
|
52
|
-
if (json.success === false) {
|
|
53
|
-
throw new Error(json.error || "API returned unsuccessful response");
|
|
54
|
-
}
|
|
55
|
-
return json;
|
|
56
|
-
}
|
|
57
|
-
const errorText = await response.text();
|
|
58
|
-
// Auth errors — fail fast, never retry
|
|
59
|
-
if (response.status === 401 || response.status === 403) {
|
|
60
|
-
throw new Error(`Authentication failed: ${response.status} ${errorText}`);
|
|
61
|
-
}
|
|
62
|
-
if (isRetryableStatus(response.status) && attempt < MAX_RETRIES) {
|
|
63
|
-
const retryAfter = response.headers.get("retry-after");
|
|
64
|
-
const parsed = retryAfter ? parseInt(retryAfter, 10) : NaN;
|
|
65
|
-
const delayMs = !isNaN(parsed)
|
|
66
|
-
? parsed * 1000
|
|
67
|
-
: RETRY_BASE_MS * Math.pow(2, attempt);
|
|
68
|
-
if (this.debug) {
|
|
69
|
-
console.error(`[Directory MCP] ${response.status} on attempt ${attempt + 1}, retrying in ${delayMs}ms`);
|
|
70
|
-
}
|
|
71
|
-
await sleep(delayMs);
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
throw new Error(`API request failed: ${response.status} ${errorText}`);
|
|
75
|
-
}
|
|
76
|
-
catch (err) {
|
|
77
|
-
lastError = err;
|
|
78
|
-
if (err.message?.includes("timed out") &&
|
|
79
|
-
attempt < MAX_RETRIES) {
|
|
80
|
-
const delayMs = RETRY_BASE_MS * Math.pow(2, attempt);
|
|
81
|
-
if (this.debug) {
|
|
82
|
-
console.error(`[Directory MCP] timeout on attempt ${attempt + 1}, retrying in ${delayMs}ms`);
|
|
83
|
-
}
|
|
84
|
-
await sleep(delayMs);
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
throw err;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
throw lastError || new Error("Request failed after retries");
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
//# sourceMappingURL=client.js.map
|
package/dist/client.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export interface ServerConfig {
|
|
2
|
-
directoryUrl: string;
|
|
3
|
-
serverName: string;
|
|
4
|
-
version: string;
|
|
5
|
-
debug: boolean;
|
|
6
|
-
}
|
|
7
|
-
export declare function loadConfig(): ServerConfig;
|
|
8
|
-
export declare function validateConfig(config: ServerConfig): void;
|
|
9
|
-
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,wBAAgB,UAAU,IAAI,YAAY,CAOzC;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAIzD"}
|
package/dist/config.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.
|
|
2
|
-
export function loadConfig() {
|
|
3
|
-
return {
|
|
4
|
-
directoryUrl: process.env.PICA_DIRECTORY_URL || "https://withpica.com",
|
|
5
|
-
serverName: "pica-directory-mcp-server",
|
|
6
|
-
version: "1.0.0",
|
|
7
|
-
debug: process.env.DEBUG === "true" || process.env.DEBUG === "1",
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
export function validateConfig(config) {
|
|
11
|
-
if (!config.directoryUrl || !config.directoryUrl.startsWith("http")) {
|
|
12
|
-
throw new Error("Invalid PICA_DIRECTORY_URL");
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAS7D,MAAM,UAAU,UAAU;IACxB,OAAO;QACL,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,sBAAsB;QACtE,UAAU,EAAE,2BAA2B;QACvC,OAAO,EAAE,OAAO;QAChB,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,GAAG;KACjE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAoB;IACjD,IAAI,CAAC,MAAM,CAAC,YAAY,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACpE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAChD,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
DELETED
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.
|
|
3
|
-
import { loadConfig, validateConfig } from "./config.js";
|
|
4
|
-
import { DirectoryMcpServer } from "./server.js";
|
|
5
|
-
async function main() {
|
|
6
|
-
try {
|
|
7
|
-
const config = loadConfig();
|
|
8
|
-
validateConfig(config);
|
|
9
|
-
const server = new DirectoryMcpServer(config);
|
|
10
|
-
await server.start();
|
|
11
|
-
process.on("SIGINT", async () => {
|
|
12
|
-
console.error("[Directory MCP] Shutting down...");
|
|
13
|
-
await server.stop();
|
|
14
|
-
process.exit(0);
|
|
15
|
-
});
|
|
16
|
-
process.on("SIGTERM", async () => {
|
|
17
|
-
console.error("[Directory MCP] Shutting down...");
|
|
18
|
-
await server.stop();
|
|
19
|
-
process.exit(0);
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
catch (error) {
|
|
23
|
-
console.error("[Directory MCP] Fatal error:", error instanceof Error ? error.message : String(error));
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
main();
|
|
28
|
-
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,6DAA6D;AAG7D,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,cAAc,CAAC,MAAM,CAAC,CAAC;QAEvB,MAAM,MAAM,GAAG,IAAI,kBAAkB,CAAC,MAAM,CAAC,CAAC;QAC9C,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QAErB,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;YAC9B,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;YAClD,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;QAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;YAC/B,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;YAClD,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CACX,8BAA8B,EAC9B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CACvD,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC"}
|
package/dist/prompts/index.d.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Prompt Registry for Directory MCP Server
|
|
3
|
-
* Guided workflows for music discovery, sync licensing, and rights research
|
|
4
|
-
*/
|
|
5
|
-
export interface PromptDefinition {
|
|
6
|
-
name: string;
|
|
7
|
-
description: string;
|
|
8
|
-
arguments?: Array<{
|
|
9
|
-
name: string;
|
|
10
|
-
description: string;
|
|
11
|
-
required?: boolean;
|
|
12
|
-
}>;
|
|
13
|
-
}
|
|
14
|
-
export interface PromptMessage {
|
|
15
|
-
role: "user" | "assistant";
|
|
16
|
-
content: {
|
|
17
|
-
type: string;
|
|
18
|
-
text: string;
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
export interface PromptResult {
|
|
22
|
-
messages: PromptMessage[];
|
|
23
|
-
[key: string]: unknown;
|
|
24
|
-
}
|
|
25
|
-
export declare class PromptRegistry {
|
|
26
|
-
listPrompts(): PromptDefinition[];
|
|
27
|
-
getPrompt(name: string, args?: Record<string, any>): Promise<PromptResult>;
|
|
28
|
-
private getFindMusicPrompt;
|
|
29
|
-
private getResearchCreatorPrompt;
|
|
30
|
-
private getDirectoryAutopilotPrompt;
|
|
31
|
-
}
|
|
32
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/prompts/index.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,KAAK,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,qBAAa,cAAc;IACzB,WAAW,IAAI,gBAAgB,EAAE;IAoC3B,SAAS,CACb,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GACzB,OAAO,CAAC,YAAY,CAAC;IAaxB,OAAO,CAAC,kBAAkB;IA6C1B,OAAO,CAAC,wBAAwB;IAwChC,OAAO,CAAC,2BAA2B;CAqCpC"}
|