Skip to main content
Glama

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
NameRequiredDescriptionDefault
includeContentNoWhether to include the full content of each NIP in the results
limitNoMaximum number of results to return
queryYesSearch query to find relevant NIPs

Implementation Reference

  • 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"}`, }, ], }; } },
  • 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"}`, }, ], }; } }, );
  • 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; }
  • 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"); }
  • 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[]; }

Other Tools

Related Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/AustinKelsay/nostr-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server