find
Search or list available MCP tools by capability. Use vector search with description parameter to find tools matching specific needs, or omit description to view all tools.
Instructions
Search or list MCP tools. Call as find() - no prefix needed. With description param: vector search tools by capability. Without description: list all available tools. Use pipe separator for multi-query ("gmail | slack").
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| description | No | Search query describing capability you need (e.g. "send email", "read file"). OMIT this parameter entirely to list all tools (do not pass "list all" or empty string). Use pipe separator for multi-query ("gmail | slack"). | |
| limit | No | Max results per page (default: 5 search, 20 list) | |
| page | No | Page number (default: 1) | |
| confidence_threshold | No | Min confidence 0.0-1.0 (default: 0.35). Lower=more results, higher=precise. | |
| depth | No | Detail level: 0=names, 1=+descriptions, 2=+parameters (default: 2) |
Implementation Reference
- src/server/mcp-server.ts:1189-1285 (handler)The handleFind() method is the main handler that executes the find tool. It processes arguments (description, limit, page, depth, confidence_threshold), handles multi-query via pipe separator, uses ToolFinder service for searching, builds structured results, and returns formatted output with optional structured content support.
private async handleFind(args: any): Promise<any> { const description = args?.description || ''; const page = Math.max(1, args?.page || 1); const limit = args?.limit || (description ? 5 : 20); const depth = args?.depth !== undefined ? Math.max(0, Math.min(2, args.depth)) : 2; const confidenceThreshold = args?.confidence_threshold !== undefined ? args.confidence_threshold : 0.35; // Check for pipe-delimited multi-query const queries = description.includes('|') ? description.split('|').map((q: string) => q.trim()).filter((q: string) => q.length > 0) : null; // Handle multi-query case if (queries && queries.length > 1) { return this.handleMultiQuery(queries, { page, limit, depth, confidenceThreshold }); } // MCP 2025-11-25: Send progress notification about indexing status const initialProgress = this.orchestrator.getIndexingProgress(); if (initialProgress && this.supportsCapability('progressNotifications')) { await this.sendProgressNotification( 'find:indexing', initialProgress.current, initialProgress.total, `Indexing: ${initialProgress.currentMCP || 'scanning'}` ); } // Use ToolFinder service for single-query search logic const finder = new ToolFinder(this.orchestrator); const findResult = await finder.find({ query: description, page, limit, depth, confidenceThreshold }); const { tools: results, pagination, mcpFilter, isListing } = findResult; // Always get indexing progress - will be null if indexing complete const progress = this.orchestrator.getIndexingProgress(); // Get health status const healthStatus = this.orchestrator.getMCPHealthStatus(); // SPECIAL CASE: No results found and user has a query - suggest MCPs from registry // Only do this if not currently indexing (progress is null or completed) if (results.length === 0 && description && !progress) { return this.handleNoResultsWithRegistryFallback(description, healthStatus); } // ALWAYS build structured data first (single source of truth) const structured = await this.buildStructuredFindResult( results, pagination, healthStatus, progress, mcpFilter, description, isListing ); // Render structured data to markdown for MCP response const text = FindResultRenderer.render(structured); // Track token usage for analytics this.tokenTracker.trackFind(text, structured.tools.length, description).catch(() => { // Ignore tracking errors - don't fail the request }); // MCP 2025-11-25: Send final progress notification if indexing is complete const finalProgress = this.orchestrator.getIndexingProgress(); if (!finalProgress && this.supportsCapability('progressNotifications')) { await this.sendProgressNotification( 'find:indexing', 100, 100, 'Indexing complete' ); } // MCP 2025-11-25: Return structured output if client supports it // This enables Claude to parse typed results instead of parsing markdown if (this.supportsCapability('structuredContent')) { logger.debug(`Returning structured find results (${structured.tools.length} tools)`); return { content: [{ type: 'text', text }], // Include text fallback for compatibility structuredContent: structured // Return typed structure for parsing }; } // Fallback: Return text-only for clients without structured support return { content: [{ type: 'text', text }] }; } - src/server/mcp-server.ts:344-415 (schema)Tool registration and schema definition for the 'find' tool. Defines inputSchema with parameters (description, limit, page, confidence_threshold, depth) and outputSchema with structured results including tools array, health status, and pagination info.
private getToolDefinitions(): Tool[] { // Define all core NCP tools with outputSchema for structured outputs const findTool: Tool = { name: 'find', description: 'Search or list MCP tools. Call as find() - no prefix needed. With description param: vector search tools by capability. Without description: list all available tools. Use pipe separator for multi-query ("gmail | slack").', inputSchema: { type: 'object', properties: { description: { type: 'string', description: 'Search query describing capability you need (e.g. "send email", "read file"). OMIT this parameter entirely to list all tools (do not pass "list all" or empty string). Use pipe separator for multi-query ("gmail | slack").' }, limit: { type: 'number', description: 'Max results per page (default: 5 search, 20 list)' }, page: { type: 'number', description: 'Page number (default: 1)' }, confidence_threshold: { type: 'number', description: 'Min confidence 0.0-1.0 (default: 0.35). Lower=more results, higher=precise.' }, depth: { type: 'number', description: 'Detail level: 0=names, 1=+descriptions, 2=+parameters (default: 2)' } } }, // MCP 2025-11-25: Declare result structure for structured outputs outputSchema: { type: 'object', title: 'ToolSearchResults', description: 'Structured tool search results with metadata', properties: { tools: { type: 'array', description: 'Array of matching tools', items: { type: 'object', properties: { name: { type: 'string', description: 'Tool identifier (mcp:tool format)' }, description: { type: 'string', description: 'Tool description' }, parameters: { type: 'object', description: 'Tool parameter schema' }, _meta: { type: 'object', description: 'Tool metadata including UI hints' } } } }, health: { type: 'object', description: 'Indexing health status', properties: { indexingInProgress: { type: 'boolean' }, indexedCount: { type: 'number' }, totalCount: { type: 'number' }, percentComplete: { type: 'number' } } }, pagination: { type: 'object', description: 'Pagination info', properties: { page: { type: 'number' }, limit: { type: 'number' }, total: { type: 'number' }, hasMore: { type: 'boolean' } } } } } }; - src/services/tool-finder.ts:42-190 (helper)ToolFinder service class that implements the core search logic. Provides find() method with options, handles pagination, MCP filtering, confidence-based ordering, and grouping results by MCP. Also provides utility methods like getSampleTools() for fallback scenarios.
export class ToolFinder { constructor(private orchestrator: NCPOrchestrator) {} /** * Main search method with all features */ async find(options: FindOptions = {}): Promise<FindResult> { const { query = '', page = 1, limit = query ? 5 : 20, depth = 2, mcpFilter = null, confidenceThreshold = 0.35 } = options; // Detect MCP-specific search if not explicitly provided const detectedMCPFilter = mcpFilter || this.detectMCPFilter(query); // Adjust search query based on MCP filter const searchQuery = detectedMCPFilter ? '' : query; // Get results with proper confidence-based ordering from orchestrator // Request enough for pagination but not excessive amounts const searchLimit = Math.min(1000, (page * limit) + 50); // Get enough for current page + buffer const allResults = await this.orchestrator.find(searchQuery, searchLimit, depth >= 1, confidenceThreshold); // Apply MCP filtering if detected const filteredResults = detectedMCPFilter ? allResults.filter(r => r.mcpName.toLowerCase() === detectedMCPFilter.toLowerCase()) : allResults; // Results are already sorted by confidence from orchestrator - maintain that order // Calculate pagination const pagination = this.calculatePagination(filteredResults.length, page, limit); // Get page results while preserving confidence-based order const pageResults = filteredResults.slice(pagination.startIndex, pagination.endIndex); // Group by MCP const groupedByMCP = this.groupByMCP(pageResults); return { tools: pageResults, groupedByMCP, pagination, mcpFilter: detectedMCPFilter, isListing: !query || query.trim() === '', query }; } /** * Calculate pagination details */ private calculatePagination(totalResults: number, page: number, limit: number): PaginationInfo { const totalPages = Math.ceil(totalResults / limit); const safePage = Math.max(1, Math.min(page, totalPages || 1)); const startIndex = (safePage - 1) * limit; const endIndex = Math.min(startIndex + limit, totalResults); return { page: safePage, totalPages, totalResults, startIndex, endIndex, resultsInPage: endIndex - startIndex }; } /** * Group tools by their MCP */ private groupByMCP(results: any[]): Record<string, GroupedTool[]> { const groups: Record<string, GroupedTool[]> = {}; results.forEach(result => { if (!groups[result.mcpName]) { groups[result.mcpName] = []; } groups[result.mcpName].push({ toolName: result.toolName, confidence: result.confidence, description: result.description, schema: result.schema }); }); return groups; } /** * Detect if query is an MCP-specific search */ private detectMCPFilter(query: string): string | null { if (!query) return null; const lowerQuery = query.toLowerCase().trim(); // Common MCP names to check const knownMCPs = [ 'filesystem', 'memory', 'shell', 'portel', 'tavily', 'desktop-commander', 'stripe', 'sequential-thinking', 'context7-mcp', 'github', 'gitlab', 'slack' ]; // Check for exact MCP name match for (const mcp of knownMCPs) { if (lowerQuery === mcp || lowerQuery === `${mcp}:`) { return mcp; } } // Check if query starts with MCP:tool pattern if (lowerQuery.includes(':')) { const [potentialMCP] = lowerQuery.split(':'); if (knownMCPs.includes(potentialMCP)) { return potentialMCP; } } return null; } /** * Get sample tools when no results found */ async getSampleTools(count: number = 8): Promise<{ mcpName: string; description: string }[]> { const sampleTools = await this.orchestrator.find('', count); const mcpSet = new Set<string>(); const samples: { mcpName: string; description: string }[] = []; // Get MCP descriptions from server info const serverDescriptions = this.orchestrator.getServerDescriptions(); for (const tool of sampleTools) { if (!mcpSet.has(tool.mcpName)) { mcpSet.add(tool.mcpName); samples.push({ mcpName: tool.mcpName, description: serverDescriptions[tool.mcpName] || tool.mcpName }); } } return samples; } - src/types/find-result.ts:1-104 (schema)Type definitions for structured find results. Defines ToolResult, HealthStatus, IndexingProgress, PaginationInfo, FindResultStructured, and MultiQueryResult interfaces that describe the structure of find tool outputs.
/** * Structured types for ncp:find results * Used in Code-Mode for programmatic access */ export interface ToolParameter { name: string; type: string; description?: string; required: boolean; } export interface ToolResult { /** Full tool identifier (mcp_name:tool_name) */ name: string; /** MCP namespace */ mcp: string; /** Tool name within MCP */ tool: string; /** Tool description */ description: string; /** Confidence score (0-1) for search results */ confidence: number; /** Tool parameters */ parameters: ToolParameter[]; /** Full JSON schema */ schema?: any; /** Health status */ healthy: boolean; } export interface MCPHealth { name: string; healthy: boolean; } export interface HealthStatus { total: number; healthy: number; unhealthy: number; mcps: MCPHealth[]; } export interface IndexingProgress { current: number; total: number; currentMCP?: string; estimatedTimeRemaining?: number; } export interface PaginationInfo { page: number; totalPages: number; totalResults: number; resultsInPage: number; } export interface FindResultStructured { /** Array of tool results */ tools: ToolResult[]; /** Pagination information */ pagination: PaginationInfo; /** Health status of MCPs */ health: HealthStatus; /** Indexing progress (if still indexing) */ indexing?: IndexingProgress; /** MCP filter applied (if any) */ mcpFilter?: string; /** Search query used */ query?: string; /** Whether this is a listing (no query) or search */ isListing: boolean; } export interface MultiQueryResult { /** Array of queries and their results */ queries: Array<{ query: string; tools: ToolResult[]; }>; /** Total tools found across all queries */ totalTools: number; /** Health status of MCPs */ health: HealthStatus; /** Indexing progress (if still indexing) */ indexing?: IndexingProgress; } - src/server/mcp-server.ts:1150-1187 (helper)handleMultiQuery() helper method that supports pipe-delimited multi-query searches (e.g., 'gmail | slack'). Executes parallel searches and combines results into a structured MultiQueryResult format.
private async handleMultiQuery(queries: string[], options: any): Promise<any> { const { limit, depth, confidenceThreshold } = options; const finder = new ToolFinder(this.orchestrator); // Execute parallel searches for all queries const searchResults = await Promise.all( queries.map(query => finder.find({ query, page: 1, limit, depth, confidenceThreshold }) ) ); // Always get indexing progress - will be null if indexing complete const progress = this.orchestrator.getIndexingProgress(); // Get health status const healthStatus = this.orchestrator.getMCPHealthStatus(); // ALWAYS build structured data first (single source of truth) const structured = await this.buildStructuredMultiQueryResult( queries, searchResults, healthStatus, progress ); // Render structured data to markdown for MCP response const text = FindResultRenderer.renderMultiQuery(structured); return { content: [{ type: 'text', text }] }; }