search
Find AI tools, frameworks, APIs, MCP servers, and agents using the Unfragile match graph. Returns ranked results with capability matches and graph signals.
Instructions
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.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | What you're looking for (e.g., 'best framework for building AI agents', 'MCP server for database access') | |
| limit | No | Max results to return | |
| type | No | Filter by artifact type |
Implementation Reference
- src/index.ts:428-445 (handler)The handler function for the 'search' tool. It receives {query, limit, type} parameters, calls searchAPI(), formats results via formatResults(), and returns the text content. This is the core execution logic of the tool.
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.", { query: z.string().min(2).max(500).describe("What you're looking for (e.g., 'best framework for building AI agents', 'MCP server for database access')"), limit: z.number().min(1).max(20).default(5).describe("Max results to return"), 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"), }, async ({ query, limit, type }) => { log("search", query); try { const data = await searchAPI(query, { limit, type }); return { content: [{ type: "text" as const, text: formatResults(data) }] }; } catch (err) { return { content: [{ type: "text" as const, text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true }; } } ); - src/index.ts:37-81 (schema)TypeScript interfaces for the search API response: SearchMatch (individual result with artifact, capabilities, matchGraph, compositeScore, matchProof) and SearchResponse (query, intent, matches, matchCount, graphSignal). These define the shape of data returned from the search API.
interface SearchMatch { artifact: { id: string; name: string; type: string; url: string; slug: string; description: string; categories: string[]; pricing: { model: string; free: boolean }; verified: boolean; unfragileRank: number; pageUrl: string; }; capabilities: Array<{ name: string; description: string; matchScore: number; bestFor: string[]; limitations: string[]; }>; matchGraph: { timesMatched: number; successRate: number; topIntents: string[]; }; compositeScore: number; matchProof?: { reasoning: string; confidence: { score: number; percentile: number; topDimension: string }; evidence: { timesMatched: number; successRate: number; topIntents: string[] }; }; } interface SearchResponse { query: string; intent: { type: string; category: string; refined: string }; matches: SearchMatch[]; matchCount: number; graphSignal: { gapDetected: boolean; queryId: string; matchRecordIds?: string[]; }; } - src/index.ts:428-445 (registration)Registration of the 'search' tool on the MCP server using server.tool(). This registers the tool name 'search', its description, input schema (zod-defined), and the async handler function.
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.", { query: z.string().min(2).max(500).describe("What you're looking for (e.g., 'best framework for building AI agents', 'MCP server for database access')"), limit: z.number().min(1).max(20).default(5).describe("Max results to return"), 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"), }, async ({ query, limit, type }) => { log("search", query); try { const data = await searchAPI(query, { limit, type }); return { content: [{ type: "text" as const, text: formatResults(data) }] }; } catch (err) { return { content: [{ type: "text" as const, text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true }; } } ); - src/index.ts:167-203 (helper)The searchAPI() helper function. Makes an HTTP GET request to the Unfragile /api/v1/search endpoint with query params (q, source, limit, type, proof=true). Handles authentication, timeout (15s), error handling, and returns parsed SearchResponse JSON.
async function searchAPI( query: string, options: { limit?: number; type?: string } = {} ): Promise<SearchResponse> { const params = new URLSearchParams({ q: query, source: SOURCE }); if (options.limit) params.set("limit", String(options.limit)); if (options.type) params.set("type", options.type); const headers: Record<string, string> = { Accept: "application/json" }; if (API_KEY) headers["X-API-Key"] = API_KEY; const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 15_000); try { const res = await fetch(`${API_BASE}/api/v1/search?${params}&proof=true`, { headers, signal: controller.signal, }); if (!res.ok) { const text = await res.text(); throw new Error(`Unfragile API error ${res.status}: ${text}`); } const contentType = res.headers.get("content-type") || ""; if (!contentType.includes("application/json")) { throw new Error( `Unfragile API returned ${contentType} instead of JSON. The API may be down or returning an error page.` ); } return res.json() as Promise<SearchResponse>; } finally { clearTimeout(timeout); } } - src/index.ts:323-346 (helper)The formatResults() helper function. Takes a SearchResponse and formats it into a human-readable markdown string showing the search query, intent, match count, and each match with capabilities, match proof, and graph signals.
function formatResults(data: SearchResponse): string { const lines: string[] = []; lines.push(`# Search: "${data.query}"`); lines.push(`Intent: ${data.intent.type} | Category: ${data.intent.category || "general"}`); lines.push(`Found: ${data.matchCount} matches\n`); if (data.matches.length === 0) { lines.push("No matches found. This gap has been recorded — the Unfragile graph learns from every query."); return lines.join("\n"); } for (let i = 0; i < data.matches.length; i++) { lines.push(formatMatch(data.matches[i], i + 1)); lines.push(""); } // Include matchRecordIds for feedback const ids = data.graphSignal.matchRecordIds; if (ids && ids.length > 0) { lines.push(`\n_Match record IDs (for feedback): ${ids.join(", ")}_`); } return lines.join("\n"); }