search_docs
Search MCP documentation by keywords or phrases to find relevant sections with context, optionally filtering by category.
Instructions
Search through MCP documentation using keywords or phrases. Returns relevant documentation sections with context.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Search query - keywords, phrases, or specific concepts to find in the documentation | |
| category | No | Optional: limit search to specific documentation category | all |
Implementation Reference
- src/tools/searchDocs.ts:141-258 (handler)The complete handler implementation for the 'search_docs' tool. It searches through markdown documentation files in the docs directory, filters by optional category, computes relevance scores, extracts matches with context, sorts results, and returns a formatted markdown response with previews and resource URIs.export const searchDocs = { name: "search_docs", description: "Search through MCP documentation using keywords or phrases. Returns relevant documentation sections with context.", inputSchema: inputSchema.shape, async execute(args: z.infer<typeof inputSchema>) { const { query, category } = args; try { // Get path to scraped_docs directory const docsPath = docsDirectory; // Read all markdown files const files = readdirSync(docsPath).filter(file => file.endsWith('.md')); const results: SearchResult[] = []; for (const file of files) { const filePath = join(docsPath, file); const content = readFileSync(filePath, 'utf-8'); const fileCategory = categorizeFile(file); // Skip if category filter is specified and doesn't match if (category !== "all" && fileCategory !== category) { continue; } const relevance = calculateRelevance(content, query); // Only include files with some relevance if (relevance > 0) { const matches = findMatches(content, query); results.push({ file, title: extractTitle(content), content: content.substring(0, 500) + (content.length > 500 ? "..." : ""), category: fileCategory, relevance, matches }); } } // Sort by relevance results.sort((a, b) => b.relevance - a.relevance); if (results.length === 0) { return { content: [{ type: "text" as const, text: `No documentation found matching "${query}"${category !== "all" ? ` in category "${category}"` : ""}. Try: - Using different keywords - Searching in "all" categories - Using broader search terms - Checking spelling Available categories: getting_started, concepts, development, specification, tools, community` }] }; } // Format results let response = `# 🔍 Search Results for "${query}"\n\n`; response += `Found ${results.length} relevant document${results.length === 1 ? '' : 's'}`; if (category !== "all") { response += ` in category "${category}"`; } response += `:\n\n`; for (let i = 0; i < Math.min(results.length, 10); i++) { const result = results[i]; response += `## ${i + 1}. ${result.title}\n`; response += `**File**: \`${result.file}\` | **Category**: ${result.category} | **Relevance**: ${result.relevance.toFixed(0)}\n\n`; if (result.matches.length > 0) { response += `**Key matches:**\n`; for (const match of result.matches.slice(0, 3)) { response += `- Line ${match.line}: "${match.text.substring(0, 100)}${match.text.length > 100 ? '...' : ''}"\n`; } response += `\n`; } // Show snippet of content response += `**Preview**: ${result.content}\n\n`; response += `**Access full document**: Use resource \`mcp-docs://${result.file}\`\n\n`; response += `---\n\n`; } if (results.length > 10) { response += `*Showing top 10 results. ${results.length - 10} additional documents found.*\n\n`; } response += `## 💡 Tips\n`; response += `- Use \`get_docs_by_category\` to explore specific areas\n`; response += `- Access full documents using the resource URIs shown above\n`; response += `- Try more specific queries for better results\n`; return { content: [{ type: "text" as const, text: response }] }; } catch (error) { return { content: [{ type: "text" as const, text: `Error searching documentation: ${error instanceof Error ? error.message : String(error)}` }] }; } } };
- src/tools/searchDocs.ts:18-29 (schema)Zod input schema defining the 'query' parameter (required) and optional 'category' filter.const inputSchema = z.object({ query: z.string().describe("Search query - keywords, phrases, or specific concepts to find in the documentation"), category: z.enum([ "all", "getting_started", "concepts", "development", "specification", "tools", "community" ]).default("all").describe("Optional: limit search to specific documentation category") });
- src/index.ts:126-154 (registration)Registration of the search_docs tool in the ListToolsRequestSchema handler, including name, description, and input schema.name: "search_docs", description: "Search through MCP documentation using keywords or phrases. Returns relevant documentation sections with context.", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query - keywords, phrases, or specific concepts to find in the documentation", }, category: { type: "string", enum: [ "all", "getting_started", "concepts", "development", "specification", "tools", "community", ], description: "Optional: limit search to specific documentation category", default: "all", }, }, required: ["query"], }, },
- src/index.ts:192-194 (registration)Dispatch/execution routing for search_docs tool in the CallToolRequestSchema switch statement.case "search_docs": return await searchDocs.execute(args as any);
- src/tools/searchDocs.ts:79-113 (helper)Helper function to calculate relevance score of a document to the search query, used in ranking results.function calculateRelevance(content: string, query: string): number { const queryLower = query.toLowerCase(); const contentLower = content.toLowerCase(); let score = 0; // Title match (high weight) const title = extractTitle(content).toLowerCase(); if (title.includes(queryLower)) { score += 100; } // Exact phrase match (high weight) const exactMatches = (contentLower.match(new RegExp(queryLower, 'g')) || []).length; score += exactMatches * 10; // Individual word matches (medium weight) const queryWords = queryLower.split(/\s+/); for (const word of queryWords) { if (word.length > 2) { const wordMatches = (contentLower.match(new RegExp(`\\b${word}\\b`, 'g')) || []).length; score += wordMatches * 2; } } // Content length penalty (prefer more focused content) const contentLength = content.length; if (contentLength > 10000) { score *= 0.8; } else if (contentLength < 1000) { score *= 1.2; } return score; }