@unfragile/mcp-server 0.2.1 → 0.3.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/README.md +12 -3
- package/dist/index.js +154 -6
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @unfragile/mcp-server
|
|
2
2
|
|
|
3
|
-
Query the [Unfragile match graph](https://unfragile.ai) from any AI agent. Find AI
|
|
3
|
+
Query the [Unfragile match graph](https://unfragile.ai) from any AI agent. Find AI artifacts, discover MCP servers, assemble stacks, compare alternatives, and fetch trust passports. Every query feeds the graph.
|
|
4
|
+
|
|
5
|
+
Unfragile is built for the agent era: humans click links, agents call capabilities. This server gives Claude Code, Cursor, Windsurf, Claude Desktop, and custom MCP clients a runtime discovery and trust layer for AI artifacts.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
@@ -62,24 +64,31 @@ Add to `~/.codeium/windsurf/mcp_config.json`:
|
|
|
62
64
|
| `search` | Find AI tools by intent. "best framework for building AI agents" |
|
|
63
65
|
| `find_mcps` | Discover MCP servers by capability. "Postgres + Slack integration" |
|
|
64
66
|
| `get_artifact` | Get full details + capabilities for a specific artifact |
|
|
67
|
+
| `resolve_capability` | Resolve a `capability://...` URI to ranked trusted artifacts |
|
|
68
|
+
| `trust_passport` | Get machine-readable trust evidence: capability URIs, permissions, data access risk, failure modes |
|
|
65
69
|
| `compare` | Compare two artifacts side-by-side |
|
|
66
70
|
| `find_stack` | Assemble a complete harness stack for a use case |
|
|
71
|
+
| `feedback` | Report success/failure to improve future routing |
|
|
72
|
+
| `subscribe` | Watch for new artifacts matching a capability need |
|
|
73
|
+
| `unsubscribe` | Cancel a monitor |
|
|
67
74
|
|
|
68
75
|
## Examples
|
|
69
76
|
|
|
70
77
|
Once installed, ask your agent:
|
|
71
78
|
|
|
72
79
|
- "Find me MCP servers for Postgres and Slack"
|
|
80
|
+
- "Resolve capability://database.postgres.query.readonly"
|
|
73
81
|
- "What's the best framework for building AI agents?"
|
|
74
82
|
- "Compare LangChain vs CrewAI"
|
|
83
|
+
- "Get the trust passport for Cursor"
|
|
75
84
|
- "I'm building a customer support agent — what tools do I need?"
|
|
76
85
|
- "Get details on Cursor's capabilities"
|
|
77
86
|
|
|
78
87
|
## How it works
|
|
79
88
|
|
|
80
|
-
The MCP server calls the [Unfragile API](https://unfragile.ai/
|
|
89
|
+
The MCP server calls the [Unfragile API](https://unfragile.ai/docs) under the hood. Search and stack queries become match records in the graph. Trust passport requests use `/api/v1/passport/{slug}` to return artifact identity, capability URIs, constraints, permissions, data access risk, observed outcomes, and UnfragileRank evidence.
|
|
81
90
|
|
|
82
|
-
|
|
91
|
+
13,160 active AI artifacts. 106,955 capabilities. 24 populated software categories. The match graph for AI.
|
|
83
92
|
|
|
84
93
|
## Environment Variables
|
|
85
94
|
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
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
|
+
// resolve_capability — Resolve capability:// URIs to trusted artifacts
|
|
14
|
+
// trust_passport — Get machine-readable trust passport for an artifact
|
|
13
15
|
// compare — Compare two artifacts side-by-side
|
|
14
16
|
// find_stack — Assemble a complete harness stack for a use case
|
|
15
17
|
// feedback — Report success/failure to close the learning loop
|
|
@@ -55,6 +57,57 @@ async function searchAPI(query, options = {}) {
|
|
|
55
57
|
clearTimeout(timeout);
|
|
56
58
|
}
|
|
57
59
|
}
|
|
60
|
+
async function passportAPI(slug) {
|
|
61
|
+
const headers = { Accept: "application/json" };
|
|
62
|
+
if (API_KEY)
|
|
63
|
+
headers["X-API-Key"] = API_KEY;
|
|
64
|
+
const controller = new AbortController();
|
|
65
|
+
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
66
|
+
try {
|
|
67
|
+
const res = await fetch(`${API_BASE}/api/v1/passport/${encodeURIComponent(slug)}`, {
|
|
68
|
+
headers,
|
|
69
|
+
signal: controller.signal,
|
|
70
|
+
});
|
|
71
|
+
if (!res.ok) {
|
|
72
|
+
const text = await res.text();
|
|
73
|
+
throw new Error(`Unfragile passport API error ${res.status}: ${text}`);
|
|
74
|
+
}
|
|
75
|
+
return res.json();
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
clearTimeout(timeout);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function resolveAPI(capability, options = {}) {
|
|
82
|
+
const params = new URLSearchParams({ capability, source: SOURCE });
|
|
83
|
+
if (options.limit)
|
|
84
|
+
params.set("limit", String(options.limit));
|
|
85
|
+
if (options.type)
|
|
86
|
+
params.set("type", options.type);
|
|
87
|
+
if (options.risk)
|
|
88
|
+
params.set("risk", options.risk);
|
|
89
|
+
if (options.passport)
|
|
90
|
+
params.set("passport", "true");
|
|
91
|
+
const headers = { Accept: "application/json" };
|
|
92
|
+
if (API_KEY)
|
|
93
|
+
headers["X-API-Key"] = API_KEY;
|
|
94
|
+
const controller = new AbortController();
|
|
95
|
+
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
96
|
+
try {
|
|
97
|
+
const res = await fetch(`${API_BASE}/api/v1/resolve?${params}`, {
|
|
98
|
+
headers,
|
|
99
|
+
signal: controller.signal,
|
|
100
|
+
});
|
|
101
|
+
if (!res.ok) {
|
|
102
|
+
const text = await res.text();
|
|
103
|
+
throw new Error(`Unfragile resolver API error ${res.status}: ${text}`);
|
|
104
|
+
}
|
|
105
|
+
return res.json();
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
clearTimeout(timeout);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
58
111
|
// ─── Formatters ──────────────────────────────────────────────
|
|
59
112
|
/** Extract a readable name from a URL when artifact.name is a raw URL */
|
|
60
113
|
function cleanName(name, url) {
|
|
@@ -135,10 +188,76 @@ function formatResults(data) {
|
|
|
135
188
|
}
|
|
136
189
|
return lines.join("\n");
|
|
137
190
|
}
|
|
191
|
+
function formatPassport(data) {
|
|
192
|
+
const p = data.unfragile;
|
|
193
|
+
const lines = [];
|
|
194
|
+
lines.push(`# Trust Passport: ${p.artifact.name}`);
|
|
195
|
+
lines.push(`**Type:** ${p.artifact.type} | **Trust Score:** ${p.trust.score}/100 | **Verified:** ${p.trust.verified ? "Yes ✓" : "No"}`);
|
|
196
|
+
lines.push(`**Data access risk:** ${p.trust.data_access_risk}`);
|
|
197
|
+
lines.push(`**URL:** ${p.artifact.url}`);
|
|
198
|
+
if (p.trust.permissions.length > 0) {
|
|
199
|
+
lines.push(`\n## Permissions / Requirements`);
|
|
200
|
+
for (const permission of p.trust.permissions)
|
|
201
|
+
lines.push(`- ${permission}`);
|
|
202
|
+
}
|
|
203
|
+
if (p.trust.failure_modes.length > 0) {
|
|
204
|
+
lines.push(`\n## Known Failure Modes`);
|
|
205
|
+
for (const failure of p.trust.failure_modes)
|
|
206
|
+
lines.push(`- ${failure}`);
|
|
207
|
+
}
|
|
208
|
+
lines.push(`\n## Observed Outcomes`);
|
|
209
|
+
lines.push(`- Matches: ${p.trust.observed_outcomes.matches}`);
|
|
210
|
+
lines.push(`- Success rate: ${Math.round(p.trust.observed_outcomes.success_rate * 100)}%`);
|
|
211
|
+
lines.push(`- Avg confidence: ${Math.round(p.trust.observed_outcomes.avg_confidence * 100)}%`);
|
|
212
|
+
if (p.trust.observed_outcomes.top_intents.length > 0) {
|
|
213
|
+
lines.push(`- Top intents: ${p.trust.observed_outcomes.top_intents.join(", ")}`);
|
|
214
|
+
}
|
|
215
|
+
if (p.capabilities.length > 0) {
|
|
216
|
+
lines.push(`\n## Capability URIs`);
|
|
217
|
+
for (const cap of p.capabilities.slice(0, 8)) {
|
|
218
|
+
lines.push(`- \`${cap.uri}\` — ${cap.name} (${Math.round(cap.confidence * 100)}% confidence)`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
lines.push(`\n→ Full passport JSON: ${data._links.self}`);
|
|
222
|
+
lines.push(`→ Artifact page: ${p.artifact.page_url}`);
|
|
223
|
+
return lines.join("\n");
|
|
224
|
+
}
|
|
225
|
+
function formatResolve(data) {
|
|
226
|
+
const lines = [];
|
|
227
|
+
lines.push(`# Resolve: ${data.capability}`);
|
|
228
|
+
lines.push(`Normalized query: ${data.normalizedQuery}`);
|
|
229
|
+
lines.push(`Mode: ${data.resolver.mode} | Risk mode: ${data.resolver.riskMode}`);
|
|
230
|
+
if (data.resolutions.length === 0) {
|
|
231
|
+
lines.push("\nNo trusted artifacts resolved for this capability. This is a demand gap.");
|
|
232
|
+
return lines.join("\n");
|
|
233
|
+
}
|
|
234
|
+
for (let i = 0; i < data.resolutions.length; i++) {
|
|
235
|
+
const r = data.resolutions[i];
|
|
236
|
+
const verified = r.artifact.verified ? " ✓" : "";
|
|
237
|
+
lines.push(`\n## ${i + 1}. ${r.artifact.name}${verified}`);
|
|
238
|
+
lines.push(`**Type:** ${r.artifact.type} | **Resolver score:** ${r.resolverScore}/100 | **Trust:** ${r.trust.score}/100 | **Risk:** ${r.dataAccessRisk}`);
|
|
239
|
+
lines.push(`**URL:** ${r.artifact.url}`);
|
|
240
|
+
if (r.capabilities.length > 0) {
|
|
241
|
+
lines.push("**Matched capabilities:**");
|
|
242
|
+
for (const cap of r.capabilities.slice(0, 4)) {
|
|
243
|
+
lines.push(`- ${cap.name} (${Math.round(cap.matchScore * 100)}% match)`);
|
|
244
|
+
if (cap.requires.length > 0)
|
|
245
|
+
lines.push(` Requires: ${cap.requires.join(", ")}`);
|
|
246
|
+
if (cap.limitations.length > 0)
|
|
247
|
+
lines.push(` Limitations: ${cap.limitations.join(", ")}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (r.trust.observedMatches > 0) {
|
|
251
|
+
lines.push(`Observed outcomes: ${r.trust.observedMatches} matches, ${Math.round(r.trust.successRate * 100)}% success`);
|
|
252
|
+
}
|
|
253
|
+
lines.push(`Passport: ${r.artifact.passportUrl}`);
|
|
254
|
+
}
|
|
255
|
+
return lines.join("\n");
|
|
256
|
+
}
|
|
138
257
|
// ─── MCP Server ──────────────────────────────────────────────
|
|
139
258
|
const server = new McpServer({
|
|
140
259
|
name: "unfragile",
|
|
141
|
-
version: "0.
|
|
260
|
+
version: "0.3.0",
|
|
142
261
|
});
|
|
143
262
|
// Tool 1: General search
|
|
144
263
|
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.", {
|
|
@@ -219,7 +338,36 @@ server.tool("get_artifact", "Get full details and capabilities for a specific AI
|
|
|
219
338
|
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
220
339
|
}
|
|
221
340
|
});
|
|
222
|
-
// Tool 4:
|
|
341
|
+
// Tool 4: Resolve capability
|
|
342
|
+
server.tool("resolve_capability", "Resolve a capability:// URI or natural-language capability into ranked AI artifacts. This is Unfragile's capability DNS primitive. Use when an agent knows what capability it needs and must choose the safest current artifact at runtime.", {
|
|
343
|
+
capability: z.string().min(2).max(500).describe("Capability URI or phrase, e.g. 'capability://database.postgres.query.readonly' or 'read-only Postgres database access'"),
|
|
344
|
+
limit: z.number().min(1).max(20).default(5).describe("Max resolutions"),
|
|
345
|
+
type: z.enum(["agent", "api", "app", "benchmark", "cli", "dataset", "extension", "finetune", "framework", "mcp", "model", "platform", "product", "prompt", "repo", "skill", "template", "webapp", "workflow"]).optional().describe("Filter by artifact type"),
|
|
346
|
+
risk: z.enum(["default", "production", "enterprise", "strict"]).default("default").describe("Risk posture. production/enterprise/strict require stronger quality/status gates."),
|
|
347
|
+
}, async ({ capability, limit, type, risk }) => {
|
|
348
|
+
log("resolve_capability", capability);
|
|
349
|
+
try {
|
|
350
|
+
const data = await resolveAPI(capability, { limit, type, risk });
|
|
351
|
+
return { content: [{ type: "text", text: formatResolve(data) }] };
|
|
352
|
+
}
|
|
353
|
+
catch (err) {
|
|
354
|
+
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
// Tool 5: Trust passport
|
|
358
|
+
server.tool("trust_passport", "Get the machine-readable trust passport for an AI artifact. Use this before an agent selects a tool, API, MCP server, model, repo, or framework for a task. Returns capability URIs, permissions, data access risk, known failure modes, observed outcomes, and trust score.", {
|
|
359
|
+
slug: z.string().min(1).max(200).describe("Artifact slug (e.g., 'cursor', 'claude-code', 'langchain')"),
|
|
360
|
+
}, async ({ slug }) => {
|
|
361
|
+
log("trust_passport", slug);
|
|
362
|
+
try {
|
|
363
|
+
const data = await passportAPI(slug);
|
|
364
|
+
return { content: [{ type: "text", text: formatPassport(data) }] };
|
|
365
|
+
}
|
|
366
|
+
catch (err) {
|
|
367
|
+
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
// Tool 6: Compare artifacts
|
|
223
371
|
server.tool("compare", "Compare two AI artifacts side-by-side. Shows capabilities, pricing, rank, and graph signals for each. Uses search-based lookup (best-effort name matching). Use this when deciding between alternatives.", {
|
|
224
372
|
artifact_a: z.string().min(1).max(200).describe("First artifact name (e.g., 'cursor')"),
|
|
225
373
|
artifact_b: z.string().min(1).max(200).describe("Second artifact name (e.g., 'windsurf')"),
|
|
@@ -265,7 +413,7 @@ server.tool("compare", "Compare two AI artifacts side-by-side. Shows capabilitie
|
|
|
265
413
|
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
266
414
|
}
|
|
267
415
|
});
|
|
268
|
-
// Tool
|
|
416
|
+
// Tool 7: Find harness stack
|
|
269
417
|
server.tool("find_stack", "Assemble a complete AI harness stack for a use case. Given a description of what you're building, returns recommended tools across harness layers: orchestration, tools/MCPs, memory, guardrails, context assembly, and evaluation. This is the key differentiator — Unfragile understands that modern AI systems are composed of 5-15 tools working together.", {
|
|
270
418
|
description: z.string().min(10).max(1000).describe("What you're building (e.g., 'a customer support agent that connects to our Postgres database and Slack, with memory of past conversations')"),
|
|
271
419
|
focus: z.enum(["full", "tools-only", "infrastructure"]).default("full").describe("Stack focus: 'full' = all layers, 'tools-only' = just MCPs and integrations, 'infrastructure' = frameworks and platforms"),
|
|
@@ -339,7 +487,7 @@ server.tool("find_stack", "Assemble a complete AI harness stack for a use case.
|
|
|
339
487
|
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
340
488
|
}
|
|
341
489
|
});
|
|
342
|
-
// Tool
|
|
490
|
+
// Tool 8: Feedback — close the learning loop
|
|
343
491
|
server.tool("feedback", "Report whether a recommended tool worked or not. This closes the learning loop — the Unfragile graph uses this feedback to improve future recommendations. Call this after trying a tool from search results.", {
|
|
344
492
|
matchRecordId: z.string().min(1).describe("Match record ID from search results (shown at the bottom of search output)"),
|
|
345
493
|
outcome: z.enum(["success", "failure"]).describe("Did the recommended tool work for your use case?"),
|
|
@@ -386,7 +534,7 @@ server.tool("feedback", "Report whether a recommended tool worked or not. This c
|
|
|
386
534
|
return { content: [{ type: "text", text: `Error sending feedback: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
387
535
|
}
|
|
388
536
|
});
|
|
389
|
-
// Tool
|
|
537
|
+
// Tool 9: Subscribe — create a monitor
|
|
390
538
|
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
539
|
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
540
|
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"),
|
|
@@ -450,7 +598,7 @@ server.tool("subscribe", "Set up a persistent watch for new AI tools matching a
|
|
|
450
598
|
};
|
|
451
599
|
}
|
|
452
600
|
});
|
|
453
|
-
// Tool
|
|
601
|
+
// Tool 10: Unsubscribe — delete a monitor
|
|
454
602
|
server.tool("unsubscribe", "Cancel a persistent watch (monitor). Use the monitor ID returned from subscribe.", {
|
|
455
603
|
monitorId: z.string().min(1).describe("Monitor ID from subscribe (e.g., 'mon_...')"),
|
|
456
604
|
}, async ({ monitorId }) => {
|
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unfragile/mcp-server",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"mcpName": "io.github.savirinc/unfragile",
|
|
5
|
+
"description": "Unfragile MCP Server — agent-native discovery, trust, and routing for AI artifacts.",
|
|
5
6
|
"keywords": [
|
|
6
7
|
"mcp",
|
|
7
8
|
"ai",
|
|
8
9
|
"tools",
|
|
9
10
|
"discovery",
|
|
11
|
+
"trust",
|
|
12
|
+
"routing",
|
|
13
|
+
"capabilities",
|
|
10
14
|
"unfragile",
|
|
11
15
|
"harness-engineering"
|
|
12
16
|
],
|