@unfragile/mcp-server 0.1.0 → 0.1.1
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/dist/index.js +36 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -33,7 +33,7 @@ async function searchAPI(query, options = {}) {
|
|
|
33
33
|
if (API_KEY)
|
|
34
34
|
headers["X-API-Key"] = API_KEY;
|
|
35
35
|
const controller = new AbortController();
|
|
36
|
-
const timeout = setTimeout(() => controller.abort(),
|
|
36
|
+
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
37
37
|
try {
|
|
38
38
|
const res = await fetch(`${API_BASE}/api/v1/search?${params}`, {
|
|
39
39
|
headers,
|
|
@@ -54,11 +54,35 @@ async function searchAPI(query, options = {}) {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
// ─── Formatters ──────────────────────────────────────────────
|
|
57
|
+
/** Extract a readable name from a URL when artifact.name is a raw URL */
|
|
58
|
+
function cleanName(name, url) {
|
|
59
|
+
if (!name || /^https?:\/\//.test(name)) {
|
|
60
|
+
// Try to derive name from URL path
|
|
61
|
+
try {
|
|
62
|
+
const u = new URL(url || name);
|
|
63
|
+
const parts = u.pathname.split("/").filter(Boolean);
|
|
64
|
+
// GitHub: owner/repo → repo
|
|
65
|
+
if (u.hostname === "github.com" && parts.length >= 2) {
|
|
66
|
+
return parts[1].replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
67
|
+
}
|
|
68
|
+
// npm/PyPI: package name
|
|
69
|
+
if (parts.length > 0) {
|
|
70
|
+
const last = parts[parts.length - 1];
|
|
71
|
+
return last.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch { }
|
|
75
|
+
// Fallback: strip protocol and trailing slash
|
|
76
|
+
return name.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
77
|
+
}
|
|
78
|
+
return name;
|
|
79
|
+
}
|
|
57
80
|
function formatMatch(m, rank) {
|
|
58
81
|
const lines = [];
|
|
82
|
+
const name = cleanName(m.artifact.name, m.artifact.url);
|
|
59
83
|
const verified = m.artifact.verified ? " ✓" : "";
|
|
60
84
|
const pricing = m.artifact.pricing.free ? "Free" : m.artifact.pricing.model;
|
|
61
|
-
lines.push(`### ${rank}. ${
|
|
85
|
+
lines.push(`### ${rank}. ${name}${verified}`);
|
|
62
86
|
lines.push(`**Type:** ${m.artifact.type} | **Score:** ${m.compositeScore}/100 | **Rank:** ${m.artifact.unfragileRank}/100 | **Pricing:** ${pricing}`);
|
|
63
87
|
lines.push(`**URL:** ${m.artifact.url}`);
|
|
64
88
|
if (m.artifact.description)
|
|
@@ -150,7 +174,8 @@ server.tool("get_artifact", "Get full details and capabilities for a specific AI
|
|
|
150
174
|
return { content: [{ type: "text", text: `No artifact found matching "${name}".` }] };
|
|
151
175
|
}
|
|
152
176
|
const lines = [];
|
|
153
|
-
|
|
177
|
+
const artName = cleanName(match.artifact.name, match.artifact.url);
|
|
178
|
+
lines.push(`# ${artName}`);
|
|
154
179
|
lines.push(`**Type:** ${match.artifact.type} | **UnfragileRank:** ${match.artifact.unfragileRank}/100`);
|
|
155
180
|
lines.push(`**URL:** ${match.artifact.url}`);
|
|
156
181
|
lines.push(`**Verified:** ${match.artifact.verified ? "Yes ✓" : "No"}`);
|
|
@@ -200,10 +225,12 @@ server.tool("compare", "Compare two AI artifacts side-by-side. Shows capabilitie
|
|
|
200
225
|
if (!matchA && !matchB) {
|
|
201
226
|
return { content: [{ type: "text", text: `Neither "${artifact_a}" nor "${artifact_b}" found.` }] };
|
|
202
227
|
}
|
|
228
|
+
const nameA = matchA ? cleanName(matchA.artifact.name, matchA.artifact.url) : artifact_a;
|
|
229
|
+
const nameB = matchB ? cleanName(matchB.artifact.name, matchB.artifact.url) : artifact_b;
|
|
203
230
|
const lines = [];
|
|
204
|
-
lines.push(`# Compare: ${
|
|
231
|
+
lines.push(`# Compare: ${nameA} vs ${nameB}\n`);
|
|
205
232
|
const row = (label, a, b) => `| ${label} | ${a} | ${b} |`;
|
|
206
|
-
lines.push(`| | ${
|
|
233
|
+
lines.push(`| | ${nameA} | ${nameB} |`);
|
|
207
234
|
lines.push(`|---|---|---|`);
|
|
208
235
|
if (matchA && matchB) {
|
|
209
236
|
lines.push(row("Type", matchA.artifact.type, matchB.artifact.type));
|
|
@@ -212,11 +239,11 @@ server.tool("compare", "Compare two AI artifacts side-by-side. Shows capabilitie
|
|
|
212
239
|
lines.push(row("Verified", matchA.artifact.verified ? "✓" : "No", matchB.artifact.verified ? "✓" : "No"));
|
|
213
240
|
lines.push(row("Times Matched", String(matchA.matchGraph.timesMatched), String(matchB.matchGraph.timesMatched)));
|
|
214
241
|
lines.push(row("Capabilities", String(matchA.capabilities.length), String(matchB.capabilities.length)));
|
|
215
|
-
lines.push(`\n## ${
|
|
242
|
+
lines.push(`\n## ${nameA} Capabilities`);
|
|
216
243
|
for (const cap of matchA.capabilities) {
|
|
217
244
|
lines.push(`- **${cap.name}**: ${cap.description.slice(0, 150)}`);
|
|
218
245
|
}
|
|
219
|
-
lines.push(`\n## ${
|
|
246
|
+
lines.push(`\n## ${nameB} Capabilities`);
|
|
220
247
|
for (const cap of matchB.capabilities) {
|
|
221
248
|
lines.push(`- **${cap.name}**: ${cap.description.slice(0, 150)}`);
|
|
222
249
|
}
|
|
@@ -275,9 +302,10 @@ server.tool("find_stack", "Assemble a complete AI harness stack for a use case.
|
|
|
275
302
|
totalTools += matches.length;
|
|
276
303
|
lines.push(`## ${layer}\n`);
|
|
277
304
|
for (const m of matches) {
|
|
305
|
+
const mName = cleanName(m.artifact.name, m.artifact.url);
|
|
278
306
|
const verified = m.artifact.verified ? " ✓" : "";
|
|
279
307
|
const pricing = m.artifact.pricing.free ? "Free" : m.artifact.pricing.model;
|
|
280
|
-
lines.push(`**${
|
|
308
|
+
lines.push(`**${mName}${verified}** — ${pricing} | Rank: ${m.artifact.unfragileRank}/100`);
|
|
281
309
|
if (m.artifact.description)
|
|
282
310
|
lines.push(`${m.artifact.description.slice(0, 200)}`);
|
|
283
311
|
if (m.capabilities.length > 0) {
|
package/package.json
CHANGED