searchNips
Find relevant Nostr Implementation Possibilities (NIPs) by submitting a search query, with options to limit results and include full content for detailed insights.
Instructions
Search through Nostr Implementation Possibilities (NIPs)
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| includeContent | No | Whether to include the full content of each NIP in the results | |
| limit | No | Maximum number of results to return | |
| query | Yes | Search query to find relevant NIPs |
Implementation Reference
- index.ts:899-939 (handler)Inline handler function for the searchNips MCP tool that calls the core searchNips function, formats results, and returns MCP-formatted content.async ({ query, limit, includeContent }) => { try { console.error(`Searching NIPs for: "${query}"`); const results = await searchNips(query, limit); if (results.length === 0) { return { content: [ { type: "text", text: `No NIPs found matching "${query}". Try different search terms or check the NIPs repository for the latest updates.`, }, ], }; } // Format results using the new formatter const formattedResults = results.map(result => formatNipResult(result, includeContent)).join("\n\n"); return { content: [ { type: "text", text: `Found ${results.length} matching NIPs:\n\n${formattedResults}`, }, ], }; } catch (error) { console.error("Error searching NIPs:", error); return { content: [ { type: "text", text: `Error searching NIPs: ${error instanceof Error ? error.message : "Unknown error"}`, }, ], }; } },
- index.ts:894-898 (schema)Zod schema defining input parameters for the searchNips tool: query (string), limit (number, 1-50 default 10), includeContent (boolean default false).{ query: z.string().describe("Search query to find relevant NIPs"), limit: z.number().min(1).max(50).default(10).describe("Maximum number of results to return"), includeContent: z.boolean().default(false).describe("Whether to include the full content of each NIP in the results"), },
- index.ts:891-940 (registration)Registration of the searchNips tool using server.tool(), including name, description, schema, and handler.server.tool( "searchNips", "Search through Nostr Implementation Possibilities (NIPs)", { query: z.string().describe("Search query to find relevant NIPs"), limit: z.number().min(1).max(50).default(10).describe("Maximum number of results to return"), includeContent: z.boolean().default(false).describe("Whether to include the full content of each NIP in the results"), }, async ({ query, limit, includeContent }) => { try { console.error(`Searching NIPs for: "${query}"`); const results = await searchNips(query, limit); if (results.length === 0) { return { content: [ { type: "text", text: `No NIPs found matching "${query}". Try different search terms or check the NIPs repository for the latest updates.`, }, ], }; } // Format results using the new formatter const formattedResults = results.map(result => formatNipResult(result, includeContent)).join("\n\n"); return { content: [ { type: "text", text: `Found ${results.length} matching NIPs:\n\n${formattedResults}`, }, ], }; } catch (error) { console.error("Error searching NIPs:", error); return { content: [ { type: "text", text: `Error searching NIPs: ${error instanceof Error ? error.message : "Unknown error"}`, }, ], }; } }, );
- nips/nips-tools.ts:572-686 (helper)Core implementation of searchNips: loads NIPs from cache/GitHub, builds search index, performs relevance scoring, handles direct NIP lookups, and returns top results.export async function searchNips(query: string, limit: number = 10): Promise<NipSearchResult[]> { // Ensure we have NIPs data and the search index is built const nips = await getNips(); if (nips.length === 0) { console.error("No NIPs available for search"); console.error('Completed searchNips with no results'); return []; } // Handle direct NIP number search as a special case (fastest path) const nipNumberMatch = query.match(/^(?:NIP-?)?(\d+)$/i); if (nipNumberMatch) { const nipNumber = parseInt(nipNumberMatch[1], 10); const directNip = nips.find(nip => nip.number === nipNumber); if (directNip) { console.error('Completed searchNips with direct match'); return [{ nip: directNip, relevance: 100, matchedTerms: [nipNumber.toString()] }]; } } // Split query into terms and filter out empty strings const searchTerms = query.split(/\s+/).filter(term => term.length > 0); // If the search terms are too short or common, warn about potential slow search if (searchTerms.some(term => term.length < 3)) { console.error('Search includes very short terms which may slow down the search'); } // Search through all NIPs efficiently const results: NipSearchResult[] = []; // Pre-filter NIPs that might be relevant based on fast checks // This avoids scoring every NIP for performance const potentialMatches = new Set<number>(); // First do a quick scan to find potential matches for (const term of searchTerms) { const lowerTerm = term.toLowerCase(); // Number match if (searchIndex.numberIndex.has(lowerTerm)) { potentialMatches.add(searchIndex.numberIndex.get(lowerTerm)!); } // Title matches const titleMatches = searchIndex.titleIndex.get(lowerTerm); if (titleMatches) { titleMatches.forEach(num => potentialMatches.add(num)); } // Description matches const descMatches = searchIndex.descriptionIndex.get(lowerTerm); if (descMatches) { descMatches.forEach(num => potentialMatches.add(num)); } // Content matches only if we have few potential matches so far if (potentialMatches.size < 50) { const contentMatches = searchIndex.contentIndex.get(lowerTerm); if (contentMatches) { contentMatches.forEach(num => potentialMatches.add(num)); } } // If we have too many potential matches, don't add more from content if (potentialMatches.size > 100) { break; } } // If no potential matches through indexing, do a linear scan if (potentialMatches.size === 0) { // Fallback: check titles directly for (const nip of nips) { for (const term of searchTerms) { if (nip.title.toLowerCase().includes(term.toLowerCase())) { potentialMatches.add(nip.number); break; } } } } // Score only the potential matches for (const nipNumber of potentialMatches) { const nip = nips.find(n => n.number === nipNumber); if (!nip) continue; const { score, matchedTerms } = calculateRelevance(nip, searchTerms); if (score > 0) { results.push({ nip, relevance: score, matchedTerms }); } } // Sort by relevance and limit results results.sort((a, b) => b.relevance - a.relevance); const limitedResults = results.slice(0, limit); return limitedResults; }
- nips/nips-tools.ts:689-708 (helper)Utility function to format individual NIP search results for display, optionally including full content.export function formatNipResult(result: NipSearchResult, includeContent: boolean = false): string { const { nip, relevance, matchedTerms } = result; const lines = [ `NIP-${nip.number}: ${nip.title}`, `Status: ${nip.status}`, nip.kind ? `Kind: ${nip.kind}` : null, `Description: ${nip.description}`, `Relevance Score: ${relevance}`, matchedTerms.length > 0 ? `Matched Terms: ${matchedTerms.join(", ")}` : null, ].filter(Boolean); if (includeContent) { lines.push("", "Content:", nip.content); } lines.push("---"); return lines.join("\n"); }
- nips/nips-tools.ts:8-24 (schema)TypeScript interfaces defining NipData and NipSearchResult used by the searchNips implementation.export interface NipData { number: number; title: string; description: string; status: "draft" | "final" | "deprecated"; kind?: number; tags?: string[]; content: string; } // Define the search result structure export interface NipSearchResult { nip: NipData; relevance: number; matchedTerms: string[]; }