Skip to main content
Glama
portel-dev

NCP - Natural Context Provider

by portel-dev

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
NameRequiredDescriptionDefault
descriptionNoSearch 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").
limitNoMax results per page (default: 5 search, 20 list)
pageNoPage number (default: 1)
confidence_thresholdNoMin confidence 0.0-1.0 (default: 0.35). Lower=more results, higher=precise.
depthNoDetail level: 0=names, 1=+descriptions, 2=+parameters (default: 2)

Implementation Reference

  • 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 }]
      };
    }
  • 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' }
              }
            }
          }
        }
      };
  • 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;
      }
  • 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;
    }
  • 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 }]
      };
    }
Install Server

Other 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/portel-dev/ncp'

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