Skip to main content
Glama
portel-dev

NCP - Natural Context Provider

by portel-dev
find-result-renderer.ts13.6 kB
/** * Renders structured FindResult to markdown text * Used for MCP and CLI text responses */ import chalk from 'chalk'; import { FindResultStructured, MultiQueryResult, ToolResult } from '../types/find-result.js'; import { ParameterPredictor } from '../utils/parameter-predictor.js'; export class FindResultRenderer { /** * Render structured find result to markdown */ static render(result: FindResultStructured): string { const { tools, pagination, health, indexing, mcpFilter, query, isListing } = result; const filterText = mcpFilter ? ` (filtered to ${mcpFilter})` : ''; // Enhanced pagination display const paginationInfo = pagination.totalPages > 1 ? ` | Page ${pagination.page} of ${pagination.totalPages} (showing ${pagination.resultsInPage} of ${pagination.totalResults} results)` : ` (${pagination.totalResults} results)`; let output: string; if (query) { const highlightedQuery = chalk.inverse(` ${query} `); output = `\n🔍 Found tools for ${highlightedQuery}${filterText}${paginationInfo}:\n\n`; } else { output = `\n🔍 Available tools${filterText}${paginationInfo}:\n\n`; } // Group tools by MCP for better organization const toolsByMcp = this.groupToolsByMcp(tools); // Add MCP health status summary if (health.total > 0) { const healthIcon = health.unhealthy > 0 ? '⚠️' : '✅'; output += `${healthIcon} **MCPs**: ${health.healthy}/${health.total} healthy`; if (health.unhealthy > 0) { const unhealthyNames = health.mcps .filter(mcp => !mcp.healthy) .map(mcp => mcp.name) .join(', '); output += ` (${unhealthyNames} unavailable)`; } output += '\n\n'; } // Add indexing progress if still indexing if (indexing) { // Show indexing message even if total is 0 (initialization phase) if (indexing.total > 0) { const percentComplete = Math.round((indexing.current / indexing.total) * 100); const remainingTime = indexing.estimatedTimeRemaining ? ` (~${Math.ceil(indexing.estimatedTimeRemaining / 1000)}s remaining)` : ''; output += `⏳ **Indexing in progress**: ${indexing.current}/${indexing.total} MCPs (${percentComplete}%)${remainingTime}\n`; output += ` Currently indexing: ${indexing.currentMCP || 'initializing...'}\n\n`; } else { // total === 0 means initialization phase (haven't determined total MCPs yet) output += `⏳ **Indexing in progress**: Initializing...\n`; output += ` Currently indexing: ${indexing.currentMCP || 'initializing...'}\n\n`; } if (tools.length > 0) { output += `📋 **Showing partial results** - more tools will become available as indexing completes.\n\n`; } else { output += `📋 **No tools available yet** - please try again in a moment as indexing progresses.\n\n`; } } // Handle no results case if (tools.length === 0) { // Don't add "No tools found" if we're still indexing - the indexing message already explains it if (!indexing || indexing.total === 0) { output += `❌ No tools found${query ? ` for "${query}"` : ''}\n`; } return output; } // Render tools based on depth (inferred from parameter availability) const depth = this.inferDepth(tools); // Render by MCP group for better organization Object.entries(toolsByMcp).forEach(([mcpName, mcpTools]) => { output += `### ${mcpName}\n\n`; if (depth === 0) { // Depth 0: Tool names only, with Code-Mode compatibility mcpTools.forEach((tool) => { const confidence = Math.round(tool.confidence * 100); const matchText = isListing ? '' : ` (${confidence}% match)`; output += `// ${tool.description || tool.name}${matchText}\n`; output += `await ${tool.mcp}.${tool.tool}();\n\n`; }); } else if (depth === 1) { // Depth 1: Tool name + description + Code-Mode example mcpTools.forEach((tool, toolIndex) => { if (toolIndex > 0) output += '\n// ---\n\n'; const confidence = Math.round(tool.confidence * 100); const matchText = isListing ? '' : ` (${confidence}% match)`; const difficulty = this.getDifficultyLevel(tool); const paramCount = (tool.parameters?.length || 0); const requiredCount = tool.parameters?.filter(p => p.required).length || 0; output += `// ${tool.name}${matchText} | ${difficulty}\n`; if (tool.description) { const cleanDescription = tool.description.replace(/^[^:]+:\s*/, '').replace(/\s+/g, ' ').trim(); output += `// ${cleanDescription}\n`; } if (paramCount > 0) { output += `// Parameters: ${requiredCount} required, ${paramCount - requiredCount} optional\n`; } // Add Code-Mode example const exampleParams = this.generateCodeModeParams(tool); output += `\n\`\`\`typescript\n`; if (exampleParams) { output += `const result = await ${tool.mcp}.${tool.tool}(${exampleParams});\n`; } else { output += `const result = await ${tool.mcp}.${tool.tool}();\n`; } output += `console.log(result);\n`; output += `\`\`\`\n`; }); } else { // Depth 2: Full details with parameters and Code-Mode example mcpTools.forEach((tool, toolIndex) => { if (toolIndex > 0) output += '\n// ---\n\n'; const confidence = Math.round(tool.confidence * 100); const matchText = isListing ? '' : ` (${confidence}% match)`; const difficulty = this.getDifficultyLevel(tool); output += `// **${tool.name}**${matchText} | ${difficulty}\n`; if (tool.description) { const cleanDescription = tool.description.replace(/^[^:]+:\s*/, '').replace(/\s+/g, ' ').trim(); output += `// ${cleanDescription}\n`; } // Parameters with descriptions if (tool.parameters && tool.parameters.length > 0) { output += `// \n// **Parameters:**\n`; const required = tool.parameters.filter(p => p.required); const optional = tool.parameters.filter(p => !p.required); if (required.length > 0) { output += `// *Required:*\n`; required.forEach(param => { const descText = param.description ? ` - ${param.description}` : ''; output += `// • ${param.name}: \`${param.type}\`${descText}\n`; }); } if (optional.length > 0) { output += `// *Optional:*\n`; optional.forEach(param => { const descText = param.description ? ` - ${param.description}` : ''; output += `// • ${param.name}: \`${param.type}\`${descText}\n`; }); } } else { output += `// No parameters required\n`; } // Add Code-Mode example const exampleParams = this.generateCodeModeParams(tool); output += `\n**Code-Mode Example:**\n`; output += `\`\`\`typescript\n`; if (exampleParams) { output += `const result = await ${tool.mcp}.${tool.tool}(${exampleParams});\n`; } else { output += `const result = await ${tool.mcp}.${tool.tool}();\n`; } output += `console.log(result);\n`; output += `\`\`\`\n`; }); } output += '\n'; }); return output; } /** * Render multi-query result to markdown */ static renderMultiQuery(result: MultiQueryResult): string { const { queries, totalTools, health, indexing } = result; let output = `\n🔍 Found tools for ${queries.length} queries:\n\n`; // Add MCP health status summary if (health.total > 0) { const healthIcon = health.unhealthy > 0 ? '⚠️' : '✅'; output += `${healthIcon} **MCPs**: ${health.healthy}/${health.total} healthy`; if (health.unhealthy > 0) { const unhealthyNames = health.mcps .filter(mcp => !mcp.healthy) .map(mcp => mcp.name) .join(', '); output += ` (${unhealthyNames} unavailable)`; } output += '\n\n'; } // Add indexing progress if still indexing if (indexing && indexing.total > 0) { const percentComplete = Math.round((indexing.current / indexing.total) * 100); const remainingTime = indexing.estimatedTimeRemaining ? ` (~${Math.ceil(indexing.estimatedTimeRemaining / 1000)}s remaining)` : ''; output += `⏳ **Indexing in progress**: ${indexing.current}/${indexing.total} MCPs (${percentComplete}%)${remainingTime}\n`; output += ` Currently indexing: ${indexing.currentMCP || 'initializing...'}\n\n`; } // Display results for each query queries.forEach((queryResult, index) => { const { query, tools } = queryResult; output += `**Query ${index + 1}:** ${chalk.inverse(` ${query} `)}\n`; if (tools.length === 0) { output += ` ❌ No tools found\n\n`; } else { output += ` Found ${tools.length} tool${tools.length > 1 ? 's' : ''}:\n`; tools.forEach((tool: ToolResult) => { const healthIcon = tool.healthy ? '✅' : '❌'; output += ` ${healthIcon} ${chalk.bold(tool.name)}`; if (tool.description) { output += ` - ${tool.description}`; } output += '\n'; }); output += '\n'; } }); // Add total tools found summary output += `\n📊 **Total**: ${totalTools} tool${totalTools !== 1 ? 's' : ''} found across ${queries.length} queries\n`; // Add Code-Mode workflow example for multi-query orchestration if (totalTools > 0) { output += `\n💡 **Code-Mode Workflow** (orchestrate all tools in one execution):\n\`\`\`typescript\n`; // Generate example for each query's top result queries.forEach((queryResult) => { const { query, tools } = queryResult; if (tools.length > 0) { const tool = tools[0]; // Generate smart variable name from query const varName = query.toLowerCase() .replace(/[^a-z0-9]+/g, '_') .replace(/^_+|_+$/g, '') .substring(0, 20); // Generate example params const exampleParams = this.generateCodeModeParams(tool); output += `// ${query}\n`; if (exampleParams) { output += `const ${varName} = await ${tool.mcp}.${tool.tool}(${exampleParams});\n`; } else { output += `const ${varName} = await ${tool.mcp}.${tool.tool}();\n`; } output += `console.log("${query}:", ${varName});\n\n`; } }); output += `\`\`\`\n`; output += `🚀 One execution, ${queries.length} capabilities - 88% fewer API calls!\n`; } return output; } /** * Infer depth from tool data */ private static inferDepth(tools: ToolResult[]): number { if (tools.length === 0) return 2; const firstTool = tools[0]; if (!firstTool.parameters || firstTool.parameters.length === 0) { // No parameters might mean depth 0 or 1 return firstTool.description ? 1 : 0; } return 2; // Has parameters, assume full depth } /** * Generate example parameters for Code-Mode from tool schema */ private static generateCodeModeParams(tool: ToolResult): string { if (!tool.parameters || tool.parameters.length === 0) return ''; const requiredParams = tool.parameters.filter(p => p.required); // Only show required params, max 2 for brevity const paramsToShow = requiredParams.length > 0 ? requiredParams.slice(0, 2) : tool.parameters.slice(0, 2); if (paramsToShow.length === 0) return ''; const predictor = new ParameterPredictor(); const paramPairs = paramsToShow.map(param => { const value = predictor.predictValue(param.name, param.type, param.description || ''); return `${param.name}: ${JSON.stringify(value)}`; }); return `{ ${paramPairs.join(', ')} }`; } /** * Group tools by their MCP for better organization */ private static groupToolsByMcp(tools: ToolResult[]): Record<string, ToolResult[]> { const grouped: Record<string, ToolResult[]> = {}; tools.forEach(tool => { const mcp = tool.mcp; if (!grouped[mcp]) { grouped[mcp] = []; } grouped[mcp].push(tool); }); // Sort MCPs alphabetically, but internal MCPs (no :) come first return Object.fromEntries( Object.entries(grouped).sort(([a], [b]) => { const aIsInternal = !a.includes(':'); const bIsInternal = !b.includes(':'); if (aIsInternal === bIsInternal) return a.localeCompare(b); return aIsInternal ? -1 : 1; }) ); } /** * Determine difficulty level based on parameters */ private static getDifficultyLevel(tool: ToolResult): string { const paramCount = tool.parameters?.length || 0; const requiredCount = tool.parameters?.filter(p => p.required).length || 0; if (paramCount === 0) { return '⚡ Simple (no params)'; } else if (requiredCount === 0 && paramCount <= 2) { return '📋 Simple (optional params)'; } else if (requiredCount <= 2 && paramCount <= 4) { return '⚙️ Moderate (2-4 params)'; } else { return '🔧 Complex (5+ params)'; } } }

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