Skip to main content
Glama
hd-deman

ArtifactHub MCP Server

by hd-deman

helm-chart-templates-fuzzy-search

Search Helm chart templates with fuzzy matching to find specific files or content within Kubernetes packages on ArtifactHub. Enter chart repository, name, and search query to locate relevant templates.

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

  • Implementation of the 'helm-chart-templates-fuzzy-search' tool handler, which uses Fuse.js to perform fuzzy searching across Helm chart template lines.
    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
    						}`,
    					},
    				],
    			};
    		}
    	}
    );
  • Registration function for the 'helm-chart-templates-fuzzy-search' tool.
    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
    							}`,
    						},
    					],
    				};
    			}
    		}
    	);
    }
Behavior2/5

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

With no annotations provided, the description carries full burden for behavioral disclosure. While it mentions 'fuzzy search' (implying approximate matching), it doesn't describe what 'fuzzy' means operationally, whether it searches both filenames and contents simultaneously, what the output format looks like, or any performance/rate limit considerations. For a search tool with zero annotation coverage, this leaves significant behavioral gaps.

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 states the core functionality without unnecessary words. It's appropriately sized and front-loaded with the essential information, making every word earn its place.

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?

For a search tool with 4 parameters, no annotations, and no output schema, the description is incomplete. It doesn't explain what 'fuzzy' means in practice, what the search results look like, whether it searches recursively through nested templates, or how it handles multiple matches. The agent would need to guess about important behavioral aspects.

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?

Schema description coverage is 100%, so the schema already documents all four parameters thoroughly. The description doesn't add any parameter-specific context beyond what's in the schema (e.g., it doesn't explain what constitutes a valid 'chartRepo' format or how 'searchQuery' fuzzy matching works). Baseline 3 is appropriate when schema does the heavy lifting.

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'), providing a specific verb+resource combination. However, it doesn't explicitly differentiate from its sibling 'helm-chart-template' (which presumably retrieves specific templates) or 'helm-chart-values-fuzzy-search' (which searches values instead of templates), missing full sibling differentiation.

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. It doesn't mention when to prefer this over 'helm-chart-template' for retrieving specific templates, or when to use 'helm-chart-values-fuzzy-search' for searching values instead of templates. There's no context about prerequisites or typical use cases.

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

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