Skip to main content
Glama
AlexW00

ArtifactHub MCP Server

by AlexW00

helm-chart-templates-fuzzy-search

Search for specific template filenames or contents within Helm charts using fuzzy matching, enabling quick discovery of relevant files and data in a specified chart repository and version.

Instructions

Fuzzy search through all template filenames/contents in a Helm chart

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
chartNameYesThe Helm chart name
chartRepoYesThe Helm chart repository name
searchQueryYesThe search query for fuzzy matching
versionNoThe chart version (optional, defaults to latest)

Implementation Reference

  • The main handler function that fetches Helm chart templates from ArtifactHub, processes them into searchable lines, performs fuzzy search using Fuse.js, groups results by template, adds context lines around matches, and formats a Markdown response with highlighted matches.
    async (args: FuzzySearchParams) => { const { chartRepo, chartName, searchQuery, version } = args; try { // First get the chart info const chartInfo = await getChartInfo(chartRepo, chartName); const packageId = chartInfo.package_id; // If version is not provided, use the latest version const chartVersion = version || chartInfo.version; // Get the templates const templatesResult = await getChartTemplates( packageId, chartVersion ); console.log( `Searching for "${searchQuery}" in ${templatesResult.templates.length} templates` ); // Process templates into line-level items for better fuzzy searching const templateLineItems: TemplateLineItem[] = []; const originalTemplates = templatesResult.templates; const allTemplateLines: string[][] = []; // Pre-process templates into lines originalTemplates.forEach((template, templateIndex) => { const lines = template.content.split("\n"); allTemplateLines[templateIndex] = lines; // Store all lines for context retrieval later lines.forEach((line, lineIndex) => { // Skip empty lines to reduce noise if (line.trim()) { templateLineItems.push({ templateName: template.name, lineNumber: lineIndex + 1, lineContent: line.trim(), templateIndex, }); } }); }); console.log( `Prepared ${templateLineItems.length} lines for fuzzy search` ); // Set up Fuse.js for fuzzy searching at line level const fuse = new Fuse(templateLineItems, { keys: ["lineContent"], includeScore: true, threshold: 0.4, isCaseSensitive: false, minMatchCharLength: 3, }); // Perform the fuzzy search const searchResults = fuse.search(searchQuery); // Group results by template for better display const resultsByTemplate = new Map<number, TemplateLineItem[]>(); searchResults.forEach((result) => { const lineItem = result.item; if (!resultsByTemplate.has(lineItem.templateIndex)) { resultsByTemplate.set(lineItem.templateIndex, []); } resultsByTemplate.get(lineItem.templateIndex)!.push(lineItem); }); // Helper function to get context lines with line numbers function getContextLines( templateIndex: number, lineNumber: number ): { beforeContext: Array<{ lineNum: number; content: string }>; afterContext: Array<{ lineNum: number; content: string }>; } { const lines = allTemplateLines[templateIndex]; const beforeContext: Array<{ lineNum: number; content: string }> = []; const afterContext: Array<{ lineNum: number; content: string }> = []; // Get lines before (adjust for 0-based index) const startLine = Math.max(0, lineNumber - 1 - CONTEXT_LINES); for (let i = startLine; i < lineNumber - 1; i++) { beforeContext.push({ lineNum: i + 1, content: lines[i], }); } // Get lines after const endLine = Math.min(lines.length, lineNumber + CONTEXT_LINES); for (let i = lineNumber; i < endLine; i++) { afterContext.push({ lineNum: i + 1, content: lines[i], }); } return { beforeContext, afterContext }; } // Format the results with matching lines and context let responseText = ""; if (resultsByTemplate.size > 0) { responseText = `# Found matches in ${resultsByTemplate.size} templates:\n\n`; Array.from(resultsByTemplate.entries()).forEach( ([templateIndex, matches], index) => { const templateName = matches[0].templateName; responseText += `## ${index + 1}. ${templateName}\n`; responseText += `### Matching lines with context (${matches.length} total matches):\n\n`; // Process each match with context const MAX_MATCHES_TO_SHOW = 5; // Limit matches to avoid very long responses const matchesToShow = matches.slice(0, MAX_MATCHES_TO_SHOW); matchesToShow.forEach((match, matchIndex) => { responseText += `#### Match ${matchIndex + 1}:\n`; responseText += "```\n"; // Get context lines const { beforeContext, afterContext } = getContextLines( match.templateIndex, match.lineNumber ); // Display before context beforeContext.forEach((line) => { responseText += `${line.lineNum}: ${line.content}\n`; }); // Display the matching line highlighted responseText += `${match.lineNumber}: ${match.lineContent} <<< MATCH\n`; // Display after context afterContext.forEach((line) => { responseText += `${line.lineNum}: ${line.content}\n`; }); responseText += "```\n\n"; }); if (matches.length > MAX_MATCHES_TO_SHOW) { responseText += `_${ matches.length - MAX_MATCHES_TO_SHOW } more matches not shown_\n\n`; } } ); } else { responseText = `No templates matching "${searchQuery}" found.`; } return { content: [ { type: "text", text: responseText, }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error performing template search: ${ (error as Error).message }`, }, ], }; } }
  • Zod input schema defining parameters: chartRepo (string), chartName (string), searchQuery (string), version (optional string).
    { chartRepo: z.string().describe("The Helm chart repository name"), chartName: z.string().describe("The Helm chart name"), searchQuery: z.string().describe("The search query for fuzzy matching"), version: z .string() .optional() .describe("The chart version (optional, defaults to latest)"), },
  • Registration function that defines and registers the tool with MCP server using server.tool(), including name, description, input schema, and handler.
    export function registerFuzzySearchTemplatesTool(server: McpServer) { return server.tool( "helm-chart-templates-fuzzy-search", "Fuzzy search through all template filenames/contents in a Helm chart", { chartRepo: z.string().describe("The Helm chart repository name"), chartName: z.string().describe("The Helm chart name"), searchQuery: z.string().describe("The search query for fuzzy matching"), version: z .string() .optional() .describe("The chart version (optional, defaults to latest)"), }, async (args: FuzzySearchParams) => { const { chartRepo, chartName, searchQuery, version } = args; try { // First get the chart info const chartInfo = await getChartInfo(chartRepo, chartName); const packageId = chartInfo.package_id; // If version is not provided, use the latest version const chartVersion = version || chartInfo.version; // Get the templates const templatesResult = await getChartTemplates( packageId, chartVersion ); console.log( `Searching for "${searchQuery}" in ${templatesResult.templates.length} templates` ); // Process templates into line-level items for better fuzzy searching const templateLineItems: TemplateLineItem[] = []; const originalTemplates = templatesResult.templates; const allTemplateLines: string[][] = []; // Pre-process templates into lines originalTemplates.forEach((template, templateIndex) => { const lines = template.content.split("\n"); allTemplateLines[templateIndex] = lines; // Store all lines for context retrieval later lines.forEach((line, lineIndex) => { // Skip empty lines to reduce noise if (line.trim()) { templateLineItems.push({ templateName: template.name, lineNumber: lineIndex + 1, lineContent: line.trim(), templateIndex, }); } }); }); console.log( `Prepared ${templateLineItems.length} lines for fuzzy search` ); // Set up Fuse.js for fuzzy searching at line level const fuse = new Fuse(templateLineItems, { keys: ["lineContent"], includeScore: true, threshold: 0.4, isCaseSensitive: false, minMatchCharLength: 3, }); // Perform the fuzzy search const searchResults = fuse.search(searchQuery); // Group results by template for better display const resultsByTemplate = new Map<number, TemplateLineItem[]>(); searchResults.forEach((result) => { const lineItem = result.item; if (!resultsByTemplate.has(lineItem.templateIndex)) { resultsByTemplate.set(lineItem.templateIndex, []); } resultsByTemplate.get(lineItem.templateIndex)!.push(lineItem); }); // Helper function to get context lines with line numbers function getContextLines( templateIndex: number, lineNumber: number ): { beforeContext: Array<{ lineNum: number; content: string }>; afterContext: Array<{ lineNum: number; content: string }>; } { const lines = allTemplateLines[templateIndex]; const beforeContext: Array<{ lineNum: number; content: string }> = []; const afterContext: Array<{ lineNum: number; content: string }> = []; // Get lines before (adjust for 0-based index) const startLine = Math.max(0, lineNumber - 1 - CONTEXT_LINES); for (let i = startLine; i < lineNumber - 1; i++) { beforeContext.push({ lineNum: i + 1, content: lines[i], }); } // Get lines after const endLine = Math.min(lines.length, lineNumber + CONTEXT_LINES); for (let i = lineNumber; i < endLine; i++) { afterContext.push({ lineNum: i + 1, content: lines[i], }); } return { beforeContext, afterContext }; } // Format the results with matching lines and context let responseText = ""; if (resultsByTemplate.size > 0) { responseText = `# Found matches in ${resultsByTemplate.size} templates:\n\n`; Array.from(resultsByTemplate.entries()).forEach( ([templateIndex, matches], index) => { const templateName = matches[0].templateName; responseText += `## ${index + 1}. ${templateName}\n`; responseText += `### Matching lines with context (${matches.length} total matches):\n\n`; // Process each match with context const MAX_MATCHES_TO_SHOW = 5; // Limit matches to avoid very long responses const matchesToShow = matches.slice(0, MAX_MATCHES_TO_SHOW); matchesToShow.forEach((match, matchIndex) => { responseText += `#### Match ${matchIndex + 1}:\n`; responseText += "```\n"; // Get context lines const { beforeContext, afterContext } = getContextLines( match.templateIndex, match.lineNumber ); // Display before context beforeContext.forEach((line) => { responseText += `${line.lineNum}: ${line.content}\n`; }); // Display the matching line highlighted responseText += `${match.lineNumber}: ${match.lineContent} <<< MATCH\n`; // Display after context afterContext.forEach((line) => { responseText += `${line.lineNum}: ${line.content}\n`; }); responseText += "```\n\n"; }); if (matches.length > MAX_MATCHES_TO_SHOW) { responseText += `_${ matches.length - MAX_MATCHES_TO_SHOW } more matches not shown_\n\n`; } } ); } else { responseText = `No templates matching "${searchQuery}" found.`; } return { content: [ { type: "text", text: responseText, }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error performing template search: ${ (error as Error).message }`, }, ], }; } } ); }
  • src/index.ts:21-21 (registration)
    Call to registerFuzzySearchTemplatesTool in the main MCP server initialization, adding the tool to the server instance.
    registerFuzzySearchTemplatesTool(server);

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/AlexW00/artifacthub-mcp'

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