Skip to main content
Glama
AlexW00

ArtifactHub MCP Server

by AlexW00

helm-chart-templates-fuzzy-search

Search Helm chart templates using fuzzy matching to find specific files or content within a chart's repository.

Instructions

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

Input Schema

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

Implementation Reference

  • Registers the "helm-chart-templates-fuzzy-search" tool with the MCP server, including description, Zod input schema, and the full handler function that implements fuzzy search logic.
    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
    							}`,
    						},
    					],
    				};
    			}
    		}
    	);
    }
  • Zod schema defining the input parameters for the tool: chartRepo, chartName, searchQuery, optional version.
    	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)"),
    },
  • Handler function that implements the fuzzy search: retrieves chart info and templates from Artifact Hub, splits templates into lines, performs Fuse.js fuzzy search on lines, groups by template, adds context lines around matches, and returns formatted Markdown text response.
    	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)
    Top-level call in main server setup to register the fuzzy search templates tool.
    registerFuzzySearchTemplatesTool(server);
  • Helper function to fetch and decode base64-encoded chart templates from Artifact Hub API.
    export async function getChartTemplates(
    	packageId: string,
    	version: string
    ): Promise<{ templates: DecodedChartTemplate[] }> {
    	const templatesUrl = `https://artifacthub.io/api/v1/packages/${packageId}/${version}/templates`;
    	const response = (await fetchFromArtifactHub(
    		templatesUrl
    	)) as ChartTemplatesResponse;
    
    	// Decode base64 data for each template
    	return {
    		templates: response.templates.map((template) => ({
    			name: template.name,
    			content: Buffer.from(template.data, "base64").toString("utf-8"),
    		})),
    	};
    }

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