@unfragile/mcp-server 0.4.0 → 0.5.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.
Files changed (3) hide show
  1. package/README.md +25 -1
  2. package/dist/index.js +120 -3
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -59,6 +59,30 @@ Add to `~/.codeium/windsurf/mcp_config.json`:
59
59
 
60
60
  ## Tools
61
61
 
62
+ ### Resolve (the headline call)
63
+
64
+ | Tool | Description |
65
+ |------|-------------|
66
+ | `unfragile_resolve` | **Sprint 4.0 headline.** Resolve an agent intent into the single best AI artifact to invoke, with an invocation-ready snippet, an Ed25519-signed trust passport, and ranked alternatives. The outside calibration layer for agent tool selection. |
67
+
68
+ Example:
69
+
70
+ ```typescript
71
+ // From any MCP-compatible agent
72
+ await mcpClient.callTool("unfragile_resolve", {
73
+ intent: "send email from agent",
74
+ context: { language: "typescript", deploymentTarget: "vercel" },
75
+ });
76
+
77
+ // Returns the single best match with:
78
+ // - Invocation snippet ready to copy/paste
79
+ // - Ed25519-signed trust passport (offline-verifiable)
80
+ // - Top 3 alternatives with why_alternative
81
+ // - matchConfidence score
82
+ ```
83
+
84
+ Use `unfragile_resolve` when an agent has decided WHAT it wants to do and needs to choose WHICH artifact to call. Use `search` (below) when an agent needs to browse alternatives rather than commit to one.
85
+
62
86
  ### Discovery and trust (human-readable)
63
87
 
64
88
  | Tool | Description |
@@ -66,7 +90,7 @@ Add to `~/.codeium/windsurf/mcp_config.json`:
66
90
  | `search` | Find AI tools by intent. "best framework for building AI agents" |
67
91
  | `find_mcps` | Discover MCP servers by capability. "Postgres + Slack integration" |
68
92
  | `get_artifact` | Get full details + capabilities for a specific artifact |
69
- | `resolve_capability` | Resolve a `capability://...` URI to ranked trusted artifacts |
93
+ | `resolve_capability` | Resolve a `capability://...` URI to ranked trusted artifacts (GET-based, formatted) |
70
94
  | `trust_passport` | Get machine-readable trust evidence: capability URIs, permissions, data access risk, failure modes |
71
95
  | `compare` | Compare two artifacts side-by-side |
72
96
  | `find_stack` | Assemble a complete harness stack for a use case |
package/dist/index.js CHANGED
@@ -7,10 +7,11 @@
7
7
  // the graph learns from every interaction.
8
8
  //
9
9
  // Tools:
10
+ // unfragile_resolve — HEADLINE: Resolve intent → invocation-ready artifact + signed passport (POST /api/v1/resolve)
10
11
  // search — Find AI tools by intent/query (with Match Proof)
11
12
  // find_mcps — Discover MCP servers by capability need
12
13
  // get_artifact — Get full details + capabilities for an artifact
13
- // resolve_capability — Resolve capability:// URIs to trusted artifacts (formatted)
14
+ // resolve_capability — Resolve capability:// URIs to trusted artifacts (formatted, GET-based)
14
15
  // trust_passport — Get machine-readable trust passport for an artifact (formatted)
15
16
  // compare — Compare two artifacts side-by-side
16
17
  // find_stack — Assemble a complete harness stack for a use case
@@ -21,7 +22,7 @@
21
22
  // Protocol-native (raw JSON, Unfragile Capability Protocol v1):
22
23
  // unfragile_validate — Validate an unfragile.yml manifest
23
24
  // unfragile_passport — Raw trust passport JSON for an artifact
24
- // unfragile_resolve_capability — Raw resolver JSON for a capability:// URI / intent
25
+ // unfragile_resolve_capability — Raw resolver JSON for a capability:// URI / intent (GET-based)
25
26
  // ─────────────────────────────────────────────────────────────
26
27
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
27
28
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -113,6 +114,42 @@ async function resolveAPI(capability, options = {}) {
113
114
  clearTimeout(timeout);
114
115
  }
115
116
  }
117
+ // POST /api/v1/resolve — the headline DNS-for-agents call.
118
+ // Takes a natural-language intent (or capability:// URI) and returns
119
+ // invocation-ready snippets + signed passports + ranked alternatives.
120
+ async function resolvePostAPI(intent, options = {}) {
121
+ const headers = {
122
+ "Content-Type": "application/json",
123
+ Accept: "application/json",
124
+ };
125
+ if (API_KEY)
126
+ headers["X-API-Key"] = API_KEY;
127
+ const body = { intent };
128
+ if (options.context)
129
+ body.context = options.context;
130
+ if (options.type)
131
+ body.type = options.type;
132
+ if (options.limit)
133
+ body.limit = options.limit;
134
+ const controller = new AbortController();
135
+ const timeout = setTimeout(() => controller.abort(), 15_000);
136
+ try {
137
+ const res = await fetch(`${API_BASE}/api/v1/resolve`, {
138
+ method: "POST",
139
+ headers,
140
+ body: JSON.stringify(body),
141
+ signal: controller.signal,
142
+ });
143
+ if (!res.ok) {
144
+ const text = await res.text();
145
+ throw new Error(`Unfragile resolve POST API error ${res.status}: ${text}`);
146
+ }
147
+ return res.json();
148
+ }
149
+ finally {
150
+ clearTimeout(timeout);
151
+ }
152
+ }
116
153
  // ─── Formatters ──────────────────────────────────────────────
117
154
  /** Extract a readable name from a URL when artifact.name is a raw URL */
118
155
  function cleanName(name, url) {
@@ -227,6 +264,58 @@ function formatPassport(data) {
227
264
  lines.push(`→ Artifact page: ${p.artifact.page_url}`);
228
265
  return lines.join("\n");
229
266
  }
267
+ function formatResolvePost(data, intent) {
268
+ const lines = [];
269
+ lines.push(`# Resolved for intent: "${intent}"`);
270
+ lines.push(`Resolver: ${data.resolverVersion} | Generated: ${data.resolvedAt}${data.fromCache ? " (cached)" : ""}`);
271
+ if (data.resolved.length === 0) {
272
+ lines.push("\nNo trusted artifact resolved for this intent. This is a demand gap — the Unfragile graph has recorded it.");
273
+ return lines.join("\n");
274
+ }
275
+ for (let i = 0; i < data.resolved.length; i++) {
276
+ const r = data.resolved[i];
277
+ const signed = r.trust.signature ? " ✓ cryptographically signed" : "";
278
+ lines.push(`\n## ${i + 1}. ${r.artifact.name}${signed}`);
279
+ lines.push(`**Type:** ${r.artifact.type} | **Match confidence:** ${Math.round(r.matchConfidence * 100)}%`);
280
+ lines.push(`**URL:** ${r.artifact.url}`);
281
+ lines.push(`\n**Capability:** ${r.capability.name}`);
282
+ lines.push(`Intent fit: "${r.capability.intent}"`);
283
+ lines.push(`\n**Invocation (${r.invocation.language}):**`);
284
+ lines.push("```");
285
+ lines.push(r.invocation.snippet);
286
+ lines.push("```");
287
+ if (r.invocation.requires && r.invocation.requires.length > 0) {
288
+ lines.push(`Requires: ${r.invocation.requires.join(", ")}`);
289
+ }
290
+ if (r.trust.verified) {
291
+ lines.push(`\n**Trust:** Verified, Ed25519-signed.`);
292
+ if (r.trust.score !== undefined)
293
+ lines.push(`Trust score: ${r.trust.score}/100`);
294
+ if (r.trust.data_access_risk)
295
+ lines.push(`Data access risk: ${r.trust.data_access_risk}`);
296
+ if (r.trust.observed_outcomes) {
297
+ const o = r.trust.observed_outcomes;
298
+ lines.push(`Observed: ${o.matches} matches, ${Math.round(o.success_rate * 100)}% success`);
299
+ }
300
+ if (r.trust.signedBy && r.trust.signature) {
301
+ lines.push(`Signed by: ${r.trust.signedBy} (sig: ${r.trust.signature.slice(0, 16)}…)`);
302
+ }
303
+ }
304
+ else {
305
+ lines.push(`\n**Trust:** Unverified — no signed passport. Verify before production use.`);
306
+ }
307
+ if (r.alternatives.length > 0) {
308
+ lines.push(`\n**Alternatives:**`);
309
+ for (const alt of r.alternatives) {
310
+ lines.push(`- ${alt.name} (${alt.slug}) — ${alt.why_alternative}`);
311
+ }
312
+ }
313
+ if (r.lastVerified) {
314
+ lines.push(`\nLast verified: ${r.lastVerified}`);
315
+ }
316
+ }
317
+ return lines.join("\n");
318
+ }
230
319
  function formatResolve(data) {
231
320
  const lines = [];
232
321
  lines.push(`# Resolve: ${data.capability}`);
@@ -262,7 +351,35 @@ function formatResolve(data) {
262
351
  // ─── MCP Server ──────────────────────────────────────────────
263
352
  const server = new McpServer({
264
353
  name: "unfragile",
265
- version: "0.4.0",
354
+ version: "0.5.0",
355
+ });
356
+ // Tool 0: Resolve (HEADLINE — DNS for agents, Sprint 4.0)
357
+ //
358
+ // This is the call most agents should make most of the time when
359
+ // they need to choose WHICH artifact to invoke for a task. Wraps
360
+ // POST /api/v1/resolve, returns invocation-ready snippets +
361
+ // Ed25519-signed trust passport + alternatives + match confidence.
362
+ server.tool("unfragile_resolve", "RESOLVE an agent intent into the single best AI artifact to invoke, with an invocation-ready snippet, a cryptographically signed trust passport, and alternatives. This is Unfragile's headline call — the outside calibration layer for agent tool selection. Use this when an agent needs to choose WHICH tool/MCP/API/framework/model to invoke for a task it has decided to do. Returns the safest current option, not a list.", {
363
+ intent: z.string().min(2).max(500).describe("Natural-language intent (e.g., 'send email from agent', 'query Postgres read-only', 'transcribe an audio file'). Can also be a capability:// URI."),
364
+ context: z.record(z.unknown()).optional().describe("Free-form constraints — project size, language, deployment target, anything the resolver should factor in"),
365
+ type: z.enum(["agent", "api", "app", "benchmark", "cli", "dataset", "extension", "finetune", "framework", "mcp", "model", "platform", "product", "prompt", "repo", "skill", "template", "webapp", "workflow"]).optional().describe("Restrict the resolved artifact to a specific type"),
366
+ limit: z.number().min(1).max(10).default(1).describe("Number of resolutions to return (default 1 — use 1 unless you need backup options inline)"),
367
+ raw: z.boolean().default(false).describe("Return raw JSON for programmatic consumption (default false returns human-readable)"),
368
+ }, async ({ intent, context, type, limit, raw }) => {
369
+ log("unfragile_resolve", intent);
370
+ try {
371
+ const data = await resolvePostAPI(intent, { context, type, limit });
372
+ const text = raw
373
+ ? JSON.stringify(data, null, 2)
374
+ : formatResolvePost(data, intent);
375
+ return { content: [{ type: "text", text }] };
376
+ }
377
+ catch (err) {
378
+ return {
379
+ content: [{ type: "text", text: `Error resolving intent: ${err instanceof Error ? err.message : String(err)}` }],
380
+ isError: true,
381
+ };
382
+ }
266
383
  });
267
384
  // Tool 1: General search
268
385
  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.", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unfragile/mcp-server",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "mcpName": "io.github.Savirinc/unfragile",
5
5
  "description": "Unfragile MCP Server — agent-native discovery, trust, and routing for AI artifacts. Implements the Unfragile Capability Protocol v1.",
6
6
  "keywords": [