@unfragile/mcp-server 0.1.1 → 0.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/dist/index.js +114 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,12 +7,14 @@
|
|
|
7
7
|
// the graph learns from every interaction.
|
|
8
8
|
//
|
|
9
9
|
// Tools:
|
|
10
|
-
// search — Find AI tools by intent/query
|
|
10
|
+
// search — Find AI tools by intent/query (with Match Proof)
|
|
11
11
|
// find_mcps — Discover MCP servers by capability need
|
|
12
12
|
// get_artifact — Get full details + capabilities for an artifact
|
|
13
13
|
// compare — Compare two artifacts side-by-side
|
|
14
14
|
// find_stack — Assemble a complete harness stack for a use case
|
|
15
15
|
// feedback — Report success/failure to close the learning loop
|
|
16
|
+
// subscribe — Set up persistent watch for new tools
|
|
17
|
+
// unsubscribe — Cancel a persistent watch
|
|
16
18
|
// ─────────────────────────────────────────────────────────────
|
|
17
19
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
18
20
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -35,7 +37,7 @@ async function searchAPI(query, options = {}) {
|
|
|
35
37
|
const controller = new AbortController();
|
|
36
38
|
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
37
39
|
try {
|
|
38
|
-
const res = await fetch(`${API_BASE}/api/v1/search?${params}`, {
|
|
40
|
+
const res = await fetch(`${API_BASE}/api/v1/search?${params}&proof=true`, {
|
|
39
41
|
headers,
|
|
40
42
|
signal: controller.signal,
|
|
41
43
|
});
|
|
@@ -99,7 +101,15 @@ function formatMatch(m, rank) {
|
|
|
99
101
|
lines.push(` Limitations: ${cap.limitations.join(", ")}`);
|
|
100
102
|
}
|
|
101
103
|
}
|
|
102
|
-
if (m.
|
|
104
|
+
if (m.matchProof) {
|
|
105
|
+
lines.push(`\n**Match Proof:**`);
|
|
106
|
+
lines.push(` ${m.matchProof.reasoning}`);
|
|
107
|
+
lines.push(` Confidence: ${m.matchProof.confidence.score}/100 (${m.matchProof.confidence.topDimension})`);
|
|
108
|
+
if (m.matchProof.evidence.timesMatched > 0) {
|
|
109
|
+
lines.push(` Evidence: ${m.matchProof.evidence.timesMatched} matches, ${Math.round(m.matchProof.evidence.successRate * 100)}% success`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else if (m.matchGraph.timesMatched > 0) {
|
|
103
113
|
lines.push(`\n**Graph Signal:** Matched ${m.matchGraph.timesMatched} times | ${Math.round(m.matchGraph.successRate * 100)}% success`);
|
|
104
114
|
}
|
|
105
115
|
lines.push(`\n→ Details: ${m.artifact.pageUrl}`);
|
|
@@ -128,7 +138,7 @@ function formatResults(data) {
|
|
|
128
138
|
// ─── MCP Server ──────────────────────────────────────────────
|
|
129
139
|
const server = new McpServer({
|
|
130
140
|
name: "unfragile",
|
|
131
|
-
version: "0.
|
|
141
|
+
version: "0.2.0",
|
|
132
142
|
});
|
|
133
143
|
// Tool 1: General search
|
|
134
144
|
server.tool("search", "Search the Unfragile match graph for AI tools, frameworks, APIs, MCP servers, agents, and more. Returns ranked results with capability matches and graph signals. Every query feeds the graph.", {
|
|
@@ -376,6 +386,106 @@ server.tool("feedback", "Report whether a recommended tool worked or not. This c
|
|
|
376
386
|
return { content: [{ type: "text", text: `Error sending feedback: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
377
387
|
}
|
|
378
388
|
});
|
|
389
|
+
// Tool 7: Subscribe — create a monitor
|
|
390
|
+
server.tool("subscribe", "Set up a persistent watch for new AI tools matching a query. Get notified daily when something new appears in the Unfragile graph. Requires at least one notification channel (email or webhook).", {
|
|
391
|
+
query: z.string().min(2).max(500).describe("What to watch for (e.g., 'new MCP server for Postgres', 'framework for building AI agents')"),
|
|
392
|
+
type: z.enum(["agent", "api", "app", "benchmark", "cli", "dataset", "extension", "finetune", "framework", "mcp", "model", "platform", "product", "prompt", "repo", "skill", "template", "webapp", "workflow"]).optional().describe("Only watch for this artifact type"),
|
|
393
|
+
email: z.string().email().optional().describe("Email address for notifications"),
|
|
394
|
+
webhook: z.string().url().optional().describe("Webhook URL for notifications (receives POST with new matches)"),
|
|
395
|
+
}, async ({ query, type, email, webhook }) => {
|
|
396
|
+
log("subscribe", query);
|
|
397
|
+
if (!email && !webhook) {
|
|
398
|
+
return {
|
|
399
|
+
content: [{ type: "text", text: "Error: At least one notification channel required. Provide 'email' and/or 'webhook'." }],
|
|
400
|
+
isError: true,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
try {
|
|
404
|
+
const headers = { "Content-Type": "application/json" };
|
|
405
|
+
if (API_KEY)
|
|
406
|
+
headers["X-API-Key"] = API_KEY;
|
|
407
|
+
const body = {
|
|
408
|
+
query,
|
|
409
|
+
notify: { email, webhook },
|
|
410
|
+
source: SOURCE,
|
|
411
|
+
};
|
|
412
|
+
if (type)
|
|
413
|
+
body.type = type;
|
|
414
|
+
const controller = new AbortController();
|
|
415
|
+
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
416
|
+
try {
|
|
417
|
+
const res = await fetch(`${API_BASE}/api/v1/monitor`, {
|
|
418
|
+
method: "POST",
|
|
419
|
+
headers,
|
|
420
|
+
body: JSON.stringify(body),
|
|
421
|
+
signal: controller.signal,
|
|
422
|
+
});
|
|
423
|
+
const data = await res.json();
|
|
424
|
+
if (!res.ok) {
|
|
425
|
+
throw new Error(data.error || `API error ${res.status}`);
|
|
426
|
+
}
|
|
427
|
+
const lines = [];
|
|
428
|
+
lines.push(`# Monitor Created`);
|
|
429
|
+
lines.push(`**ID:** ${data.id}`);
|
|
430
|
+
lines.push(`**Watching for:** ${data.query}`);
|
|
431
|
+
if (data.typeFilter)
|
|
432
|
+
lines.push(`**Type filter:** ${data.typeFilter}`);
|
|
433
|
+
lines.push(`**Frequency:** Daily`);
|
|
434
|
+
if (email)
|
|
435
|
+
lines.push(`**Email alerts:** ${email}`);
|
|
436
|
+
if (webhook)
|
|
437
|
+
lines.push(`**Webhook:** ${webhook}`);
|
|
438
|
+
lines.push(`**Expires:** ${data.expiresAt.slice(0, 10)} (90 days)`);
|
|
439
|
+
lines.push(`\n_Save the monitor ID to unsubscribe later._`);
|
|
440
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
441
|
+
}
|
|
442
|
+
finally {
|
|
443
|
+
clearTimeout(timeout);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
catch (err) {
|
|
447
|
+
return {
|
|
448
|
+
content: [{ type: "text", text: `Error creating monitor: ${err instanceof Error ? err.message : String(err)}` }],
|
|
449
|
+
isError: true,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
// Tool 8: Unsubscribe — delete a monitor
|
|
454
|
+
server.tool("unsubscribe", "Cancel a persistent watch (monitor). Use the monitor ID returned from subscribe.", {
|
|
455
|
+
monitorId: z.string().min(1).describe("Monitor ID from subscribe (e.g., 'mon_...')"),
|
|
456
|
+
}, async ({ monitorId }) => {
|
|
457
|
+
log("unsubscribe", monitorId);
|
|
458
|
+
try {
|
|
459
|
+
const headers = {};
|
|
460
|
+
if (API_KEY)
|
|
461
|
+
headers["X-API-Key"] = API_KEY;
|
|
462
|
+
const controller = new AbortController();
|
|
463
|
+
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
464
|
+
try {
|
|
465
|
+
const res = await fetch(`${API_BASE}/api/v1/monitor?id=${encodeURIComponent(monitorId)}`, {
|
|
466
|
+
method: "DELETE",
|
|
467
|
+
headers,
|
|
468
|
+
signal: controller.signal,
|
|
469
|
+
});
|
|
470
|
+
if (!res.ok) {
|
|
471
|
+
const data = await res.json();
|
|
472
|
+
throw new Error(data.error || `API error ${res.status}`);
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
content: [{ type: "text", text: `Monitor ${monitorId} cancelled. You will no longer receive alerts for this watch.` }],
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
finally {
|
|
479
|
+
clearTimeout(timeout);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
catch (err) {
|
|
483
|
+
return {
|
|
484
|
+
content: [{ type: "text", text: `Error cancelling monitor: ${err instanceof Error ? err.message : String(err)}` }],
|
|
485
|
+
isError: true,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
});
|
|
379
489
|
// ─── Start ───────────────────────────────────────────────────
|
|
380
490
|
async function main() {
|
|
381
491
|
const transport = new StdioServerTransport();
|
package/package.json
CHANGED