@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.
- package/CHANGELOG.md +56 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1 -0
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +9 -12
- package/dist/prompts/index.js.map +1 -1
- package/dist/prompts/public-question-atlas.d.ts +121 -0
- package/dist/prompts/public-question-atlas.d.ts.map +1 -0
- package/dist/prompts/public-question-atlas.js +404 -0
- package/dist/prompts/public-question-atlas.js.map +1 -0
- package/dist/resources/llms-primer.d.ts.map +1 -1
- package/dist/resources/llms-primer.js +1 -0
- package/dist/resources/llms-primer.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -0
- package/dist/server.js.map +1 -1
- package/dist/tools/chain.d.ts +12 -0
- package/dist/tools/chain.d.ts.map +1 -0
- package/dist/tools/chain.js +109 -0
- package/dist/tools/chain.js.map +1 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +3 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/people.d.ts +0 -1
- package/dist/tools/people.d.ts.map +1 -1
- package/dist/tools/people.js +24 -36
- package/dist/tools/people.js.map +1 -1
- package/dist/tools/recordings.d.ts.map +1 -1
- package/dist/tools/recordings.js +8 -3
- package/dist/tools/recordings.js.map +1 -1
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +8 -4
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/works.d.ts +0 -1
- package/dist/tools/works.d.ts.map +1 -1
- package/dist/tools/works.js +42 -42
- package/dist/tools/works.js.map +1 -1
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +1 -0
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/formatting.d.ts.map +1 -1
- package/dist/utils/formatting.js +1 -0
- package/dist/utils/formatting.js.map +1 -1
- package/jest.config.js +31 -0
- package/package.json +3 -2
- package/src/__tests__/prompts/index.test.ts +128 -0
- package/src/__tests__/prompts/prompt-eval-harness.test.ts +282 -0
- package/src/__tests__/tools/chain.test.ts +122 -0
- package/src/__tests__/tools/composability-chains.test.ts +100 -0
- package/src/__tests__/tools/people.test.ts +112 -0
- package/src/__tests__/tools/search.test.ts +94 -0
- package/src/__tests__/tools/works.test.ts +177 -0
- package/src/client.ts +128 -0
- package/src/config.ts +23 -0
- package/src/index.ts +36 -0
- package/src/prompts/index.ts +206 -0
- package/src/prompts/public-question-atlas.ts +540 -0
- package/src/resources/llms-primer.ts +35 -0
- package/src/server.ts +134 -0
- package/src/tools/chain.ts +118 -0
- package/src/tools/index.ts +83 -0
- package/src/tools/people.ts +196 -0
- package/src/tools/recordings.ts +149 -0
- package/src/tools/search.ts +66 -0
- package/src/tools/works.ts +266 -0
- package/src/utils/errors.ts +64 -0
- package/src/utils/formatting.ts +28 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import { DirectoryClient } from "../client.js";
|
|
4
|
+
import { ToolDefinition, ToolExecutor } from "./index.js";
|
|
5
|
+
import { formatStructuredList } from "../utils/formatting.js";
|
|
6
|
+
|
|
7
|
+
export class DirectorySearchTools {
|
|
8
|
+
private client: DirectoryClient;
|
|
9
|
+
|
|
10
|
+
constructor(client: DirectoryClient) {
|
|
11
|
+
this.client = client;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getTools(): Array<{ definition: ToolDefinition; executor: ToolExecutor }> {
|
|
15
|
+
return [
|
|
16
|
+
{
|
|
17
|
+
definition: {
|
|
18
|
+
name: "directory_search",
|
|
19
|
+
tier: "read",
|
|
20
|
+
description:
|
|
21
|
+
"Use when the user asks: 'find me X', 'who or what is X?', " +
|
|
22
|
+
"'find any record of X'. " +
|
|
23
|
+
"Use when the entity type is not yet known. " +
|
|
24
|
+
"Convenience fan-out across works and creators — runs list_works and list_people with the same query and returns the union. " +
|
|
25
|
+
"No cross-type ranking; prefer list_works or list_people when the entity type is known. " +
|
|
26
|
+
"→ then: directory_lookup_work (work details), directory_lookup_person (creator details)",
|
|
27
|
+
inputSchema: {
|
|
28
|
+
type: "object",
|
|
29
|
+
properties: {
|
|
30
|
+
query: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description:
|
|
33
|
+
"Search query — matches work titles, ISWCs, creator names, ISNIs",
|
|
34
|
+
},
|
|
35
|
+
type: {
|
|
36
|
+
type: "string",
|
|
37
|
+
enum: ["works", "people"],
|
|
38
|
+
description: "Limit results to a specific entity type",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
required: ["query"],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
executor: this.search.bind(this),
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private async search(args: Record<string, any>): Promise<any> {
|
|
50
|
+
const params: Record<string, string> = {
|
|
51
|
+
q: args.query,
|
|
52
|
+
limit: "20",
|
|
53
|
+
offset: "0",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (args.type) params.type = args.type;
|
|
57
|
+
|
|
58
|
+
const response: any = await this.client.request("/search", params);
|
|
59
|
+
|
|
60
|
+
return formatStructuredList(response.data || [], "result", {
|
|
61
|
+
query: args.query,
|
|
62
|
+
type: args.type || "all",
|
|
63
|
+
total: response.pagination?.total || 0,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import { DirectoryClient } from "../client.js";
|
|
4
|
+
import { ToolDefinition, ToolExecutor } from "./index.js";
|
|
5
|
+
import { formatStructuredList } from "../utils/formatting.js";
|
|
6
|
+
|
|
7
|
+
export class DirectoryWorksTools {
|
|
8
|
+
private client: DirectoryClient;
|
|
9
|
+
|
|
10
|
+
constructor(client: DirectoryClient) {
|
|
11
|
+
this.client = client;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getTools(): Array<{ definition: ToolDefinition; executor: ToolExecutor }> {
|
|
15
|
+
return [
|
|
16
|
+
{
|
|
17
|
+
definition: {
|
|
18
|
+
name: "directory_list_works",
|
|
19
|
+
tier: "read",
|
|
20
|
+
description:
|
|
21
|
+
"Use when the user asks: 'list works in PICA', 'works starting with B', " +
|
|
22
|
+
"'find works by publisher X', 'browse the catalog'. " +
|
|
23
|
+
"Browse and filter verified musical works in the PICA public directory. " +
|
|
24
|
+
"Filter by free-text (q), ISWC, ISRC, publisher, label, or starting letter (A–Z, or '#' for numeric). " +
|
|
25
|
+
"Sort by title (default) or recent. Returns paginated results. " +
|
|
26
|
+
"→ then: directory_lookup_work (full details with credits and audio)",
|
|
27
|
+
inputSchema: {
|
|
28
|
+
type: "object",
|
|
29
|
+
properties: {
|
|
30
|
+
q: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "Text search across work titles and identifiers",
|
|
33
|
+
},
|
|
34
|
+
iswc: {
|
|
35
|
+
type: "string",
|
|
36
|
+
description: "Filter by ISWC (e.g. T-123.456.789-0)",
|
|
37
|
+
},
|
|
38
|
+
isrc: {
|
|
39
|
+
type: "string",
|
|
40
|
+
description:
|
|
41
|
+
"Filter by ISRC (exact match, cross-table recording lookup)",
|
|
42
|
+
},
|
|
43
|
+
publisher: {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "Filter by publisher name (partial match)",
|
|
46
|
+
},
|
|
47
|
+
label: {
|
|
48
|
+
type: "string",
|
|
49
|
+
description: "Filter by record label (partial match)",
|
|
50
|
+
},
|
|
51
|
+
letter: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description:
|
|
54
|
+
"Filter by title starting letter (A–Z), or '#' for numeric",
|
|
55
|
+
},
|
|
56
|
+
sort: {
|
|
57
|
+
type: "string",
|
|
58
|
+
enum: ["title", "recent"],
|
|
59
|
+
description: "Sort order (default: title)",
|
|
60
|
+
},
|
|
61
|
+
page: {
|
|
62
|
+
type: "number",
|
|
63
|
+
description: "Page number (default: 1, 20 results per page)",
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
executor: this.listWorks.bind(this),
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
definition: {
|
|
72
|
+
name: "directory_lookup_work",
|
|
73
|
+
tier: "read",
|
|
74
|
+
description:
|
|
75
|
+
"Use when the user asks: 'what's the ISWC for X?', 'who wrote X?', " +
|
|
76
|
+
"'tell me about song X', 'is this song registered?'. " +
|
|
77
|
+
"Get full details of a single work by ISWC or work UUID. " +
|
|
78
|
+
"Returns credits with IPI numbers and attestation status, recordings with ISRCs, " +
|
|
79
|
+
"audio analysis (BPM, key, energy), DSP links (Spotify, Apple Music), " +
|
|
80
|
+
"AI provenance, and registration score as a structured markdown summary. " +
|
|
81
|
+
"→ then: directory_lookup_person (research a credited writer), directory_search_recordings (find similar tracks by audio)",
|
|
82
|
+
inputSchema: {
|
|
83
|
+
type: "object",
|
|
84
|
+
properties: {
|
|
85
|
+
identifier: {
|
|
86
|
+
type: "string",
|
|
87
|
+
description: "ISWC (e.g. T-123.456.789-0) or work UUID",
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
required: ["identifier"],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
executor: this.lookupWork.bind(this),
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
definition: {
|
|
97
|
+
name: "directory_lookup_isrc",
|
|
98
|
+
tier: "read",
|
|
99
|
+
description:
|
|
100
|
+
"Use when the user asks: 'lookup ISRC X', 'what's the work for ISRC X?', " +
|
|
101
|
+
"'find the work for this recording'. " +
|
|
102
|
+
"Shortcut: look up the work(s) associated with a recording identifier (ISRC). " +
|
|
103
|
+
"Equivalent to directory_list_works with an isrc filter, pinned for recording-first discovery. " +
|
|
104
|
+
"→ then: directory_lookup_work (full details on the matched work)",
|
|
105
|
+
inputSchema: {
|
|
106
|
+
type: "object",
|
|
107
|
+
properties: {
|
|
108
|
+
isrc: {
|
|
109
|
+
type: "string",
|
|
110
|
+
description: "ISRC code (e.g. USABC1234567)",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
required: ["isrc"],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
executor: this.lookupIsrc.bind(this),
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private async listWorks(args: Record<string, any>): Promise<any> {
|
|
122
|
+
const page = Math.max(args.page || 1, 1);
|
|
123
|
+
const limit = 20;
|
|
124
|
+
const offset = (page - 1) * limit;
|
|
125
|
+
|
|
126
|
+
const params: Record<string, string> = {
|
|
127
|
+
limit: String(limit),
|
|
128
|
+
offset: String(offset),
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
if (args.q) params.q = args.q;
|
|
132
|
+
if (args.iswc) params.iswc = args.iswc;
|
|
133
|
+
if (args.isrc) params.isrc = args.isrc;
|
|
134
|
+
if (args.publisher) params.publisher = args.publisher;
|
|
135
|
+
if (args.label) params.label = args.label;
|
|
136
|
+
if (args.letter) params.letter = args.letter;
|
|
137
|
+
if (args.sort) params.sort = args.sort;
|
|
138
|
+
|
|
139
|
+
const response: any = await this.client.request("/works", params);
|
|
140
|
+
|
|
141
|
+
return formatStructuredList(response.data || [], "work", {
|
|
142
|
+
page,
|
|
143
|
+
total: response.pagination?.total || 0,
|
|
144
|
+
total_pages: Math.ceil((response.pagination?.total || 0) / limit),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private async lookupWork(args: Record<string, any>): Promise<any> {
|
|
149
|
+
const response: any = await this.client.request(
|
|
150
|
+
`/works/${encodeURIComponent(args.identifier)}`,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const data = response.data;
|
|
154
|
+
if (!data) {
|
|
155
|
+
return {
|
|
156
|
+
content: [{ type: "text", text: "Work not found." }],
|
|
157
|
+
structuredContent: {},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const lines: string[] = [];
|
|
162
|
+
|
|
163
|
+
const artistName = data.artist_name || data.primary_artist || "";
|
|
164
|
+
lines.push(`## ${data.title}${artistName ? ` by ${artistName}` : ""}`);
|
|
165
|
+
lines.push("");
|
|
166
|
+
|
|
167
|
+
const metaParts: string[] = [];
|
|
168
|
+
if (data.iswc) metaParts.push(`**ISWC:** ${data.iswc}`);
|
|
169
|
+
if (data.organisation_name)
|
|
170
|
+
metaParts.push(`**Organisation:** ${data.organisation_name}`);
|
|
171
|
+
if (metaParts.length > 0) lines.push(metaParts.join(" | "));
|
|
172
|
+
|
|
173
|
+
if (data.score !== undefined && data.score !== null) {
|
|
174
|
+
const tier =
|
|
175
|
+
data.score >= 80
|
|
176
|
+
? "verified"
|
|
177
|
+
: data.score >= 50
|
|
178
|
+
? "partial"
|
|
179
|
+
: "incomplete";
|
|
180
|
+
lines.push(`- **Score:** ${data.score}/100 (${tier})`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
lines.push("");
|
|
184
|
+
|
|
185
|
+
const credits: any[] = data.credits || [];
|
|
186
|
+
lines.push(`### Credits (${credits.length})`);
|
|
187
|
+
if (credits.length > 0) {
|
|
188
|
+
lines.push("| Role | Name | IPI | Attestation |");
|
|
189
|
+
lines.push("| --- | --- | --- | --- |");
|
|
190
|
+
for (const c of credits) {
|
|
191
|
+
const role = c.role || c.credit_role || "—";
|
|
192
|
+
const name = c.name || c.person_name || "—";
|
|
193
|
+
const ipi = c.ipi || c.ipi_number || "—";
|
|
194
|
+
const attestation = c.attestation_status || c.attestation || "—";
|
|
195
|
+
lines.push(`| ${role} | ${name} | ${ipi} | ${attestation} |`);
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
lines.push("_No credits recorded._");
|
|
199
|
+
}
|
|
200
|
+
lines.push("");
|
|
201
|
+
|
|
202
|
+
const recordings: any[] = data.recordings || [];
|
|
203
|
+
lines.push(`### Recordings (${recordings.length})`);
|
|
204
|
+
if (recordings.length > 0) {
|
|
205
|
+
lines.push("| ISRC | Type |");
|
|
206
|
+
lines.push("| --- | --- |");
|
|
207
|
+
for (const r of recordings) {
|
|
208
|
+
const isrc = r.isrc || "—";
|
|
209
|
+
const type = r.recording_type || r.type || "—";
|
|
210
|
+
lines.push(`| ${isrc} | ${type} |`);
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
lines.push("_No recordings linked._");
|
|
214
|
+
}
|
|
215
|
+
lines.push("");
|
|
216
|
+
|
|
217
|
+
const audio = data.audio_analysis || data.audio || null;
|
|
218
|
+
if (audio) {
|
|
219
|
+
lines.push("### Audio");
|
|
220
|
+
const audioParts: string[] = [];
|
|
221
|
+
if (audio.bpm !== undefined && audio.bpm !== null)
|
|
222
|
+
audioParts.push(`**BPM:** ${audio.bpm}`);
|
|
223
|
+
if (audio.key) audioParts.push(`**Key:** ${audio.key}`);
|
|
224
|
+
if (audio.energy !== undefined && audio.energy !== null)
|
|
225
|
+
audioParts.push(`**Energy:** ${audio.energy}`);
|
|
226
|
+
if (audioParts.length > 0) {
|
|
227
|
+
lines.push(`- ${audioParts.join(" | ")}`);
|
|
228
|
+
}
|
|
229
|
+
lines.push("");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const dsp = data.dsp_links || data.streaming_links || null;
|
|
233
|
+
if (dsp && typeof dsp === "object" && Object.keys(dsp).length > 0) {
|
|
234
|
+
lines.push("### DSP Links");
|
|
235
|
+
if (dsp.spotify) lines.push(`- Spotify: ${dsp.spotify}`);
|
|
236
|
+
if (dsp.apple_music) lines.push(`- Apple Music: ${dsp.apple_music}`);
|
|
237
|
+
if (dsp.youtube) lines.push(`- YouTube: ${dsp.youtube}`);
|
|
238
|
+
for (const [key, val] of Object.entries(dsp)) {
|
|
239
|
+
if (!["spotify", "apple_music", "youtube"].includes(key) && val) {
|
|
240
|
+
lines.push(`- ${key}: ${val}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
lines.push("");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const markdown = lines.join("\n");
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
content: [{ type: "text", text: markdown }],
|
|
250
|
+
structuredContent: data as Record<string, unknown>,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private async lookupIsrc(args: Record<string, any>): Promise<any> {
|
|
255
|
+
const response: any = await this.client.request("/works", {
|
|
256
|
+
isrc: args.isrc,
|
|
257
|
+
limit: "100",
|
|
258
|
+
offset: "0",
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
return formatStructuredList(response.data || [], "work", {
|
|
262
|
+
isrc: args.isrc,
|
|
263
|
+
total: response.pagination?.total || 0,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.
|
|
2
|
+
|
|
3
|
+
export class McpServerError extends Error {
|
|
4
|
+
constructor(
|
|
5
|
+
message: string,
|
|
6
|
+
public code: string,
|
|
7
|
+
public details?: any,
|
|
8
|
+
) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "McpServerError";
|
|
11
|
+
Object.setPrototypeOf(this, McpServerError.prototype);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class ToolExecutionError extends McpServerError {
|
|
16
|
+
constructor(message: string, details?: any) {
|
|
17
|
+
super(message, "TOOL_EXECUTION_ERROR", details);
|
|
18
|
+
this.name = "ToolExecutionError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function formatError(error: any): { type: string; text: string } {
|
|
23
|
+
if (error instanceof McpServerError) {
|
|
24
|
+
return {
|
|
25
|
+
type: "text",
|
|
26
|
+
text: JSON.stringify(
|
|
27
|
+
{ error: error.code, message: error.message, details: error.details },
|
|
28
|
+
null,
|
|
29
|
+
2,
|
|
30
|
+
),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (error instanceof Error) {
|
|
35
|
+
return {
|
|
36
|
+
type: "text",
|
|
37
|
+
text: JSON.stringify(
|
|
38
|
+
{ error: "UNKNOWN_ERROR", message: error.message },
|
|
39
|
+
null,
|
|
40
|
+
2,
|
|
41
|
+
),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
type: "text",
|
|
47
|
+
text: JSON.stringify(
|
|
48
|
+
{ error: "UNKNOWN_ERROR", message: String(error) },
|
|
49
|
+
null,
|
|
50
|
+
2,
|
|
51
|
+
),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function logError(context: string, error: any): void {
|
|
56
|
+
const entry: Record<string, unknown> = {
|
|
57
|
+
level: "error",
|
|
58
|
+
context,
|
|
59
|
+
message: error instanceof Error ? error.message : String(error),
|
|
60
|
+
timestamp: new Date().toISOString(),
|
|
61
|
+
};
|
|
62
|
+
if (error instanceof Error && error.stack) entry.stack = error.stack;
|
|
63
|
+
console.error(JSON.stringify(entry));
|
|
64
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 Withpica Ltd. All rights reserved.
|
|
2
|
+
|
|
3
|
+
export interface FormattedResult {
|
|
4
|
+
content: Array<{ type: string; text: string }>;
|
|
5
|
+
structuredContent?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function formatAsText(data: any): FormattedResult {
|
|
9
|
+
return {
|
|
10
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
11
|
+
structuredContent: data as Record<string, unknown>,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function formatStructuredList<T>(
|
|
16
|
+
items: T[],
|
|
17
|
+
entityName: string,
|
|
18
|
+
metadata?: Record<string, any>,
|
|
19
|
+
): FormattedResult {
|
|
20
|
+
const count = items.length;
|
|
21
|
+
const summary = `Found ${count} ${entityName}${count !== 1 ? "s" : ""}${
|
|
22
|
+
metadata?.query ? ` matching "${metadata.query}"` : ""
|
|
23
|
+
}.`;
|
|
24
|
+
return {
|
|
25
|
+
content: [{ type: "text", text: summary }],
|
|
26
|
+
structuredContent: { count, items, ...metadata } as Record<string, unknown>,
|
|
27
|
+
};
|
|
28
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "Node16",
|
|
5
|
+
"moduleResolution": "Node16",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"declarationMap": true,
|
|
16
|
+
"sourceMap": true,
|
|
17
|
+
"isolatedModules": true,
|
|
18
|
+
"types": ["node", "jest"]
|
|
19
|
+
},
|
|
20
|
+
"include": ["src/**/*"],
|
|
21
|
+
"exclude": ["node_modules", "dist", "src/__tests__"]
|
|
22
|
+
}
|