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"),
    		})),
    	};
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions 'fuzzy search' but doesn't explain what that entails (e.g., tolerance levels, ranking), whether it's read-only or has side effects, or any performance considerations like rate limits. This leaves key behavioral traits unspecified.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that directly states the tool's function without unnecessary words. It's front-loaded with the core action and target, making it easy to understand quickly, with no wasted information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity of a search tool with 4 parameters and no output schema, the description is insufficient. It doesn't cover what the search returns (e.g., matches, scores), how results are formatted, or error conditions. With no annotations to fill gaps, this leaves the agent with incomplete context for effective use.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% description coverage, so the schema already documents all parameters (chartRepo, chartName, searchQuery, version). The description adds no additional meaning beyond what's in the schema, such as examples or format details, meeting the baseline for high schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('fuzzy search') and target ('through all template filenames/contents in a Helm chart'), making the purpose evident. However, it doesn't explicitly differentiate from its sibling 'helm-chart-values-fuzzy-search', which searches values instead of templates, leaving some ambiguity about sibling distinction.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives like 'helm-chart-info' or 'helm-chart-template'. It lacks context about prerequisites, such as needing a chart repository or when fuzzy searching is preferred over exact matching, offering minimal usage direction.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

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

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