Skip to main content
Glama
jake-mok-nelson

GitHub Support Assistant

find-similar-issues

Identify similar GitHub issues by comparing new issue descriptions to existing ones, helping prioritize and resolve problems efficiently within a repository.

Instructions

Find GitHub issues similar to a new issue description

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
issueDescriptionYesDescription of the issue to find similar ones for
maxResultsNoMaximum number of similar issues to return
ownerYesGitHub repository owner/organization
repoYesGitHub repository name

Implementation Reference

  • The main handler function that implements the 'find-similar-issues' tool logic, searching GitHub for similar issues using keyword extraction, API search, similarity scoring, and formatting results.
    async ({ owner, repo, issueDescription, maxResults }) => {
        // Combine title and description for better search results
        const searchText = `${issueDescription}`;
        
        // Extract important keywords for search (simple approach)
        const keywords = searchText
            .toLowerCase()
            .replace(/[^\w\s]/g, '')
            .split(/\s+/)
            .filter(word => word.length > 3)  // Filter out short words
            .filter(word => !['the', 'and', 'that', 'this', 'with'].includes(word))  // Filter common words
            .slice(0, 10)  // Limit number of keywords
            .join(' ');
            
        // Search for issues in the repository
        const searchParams = {
            q: `repo:${owner}/${repo} ${keywords}`,
            sort: 'updated',
            order: 'desc',
            per_page: '30'  // Get more results to filter by similarity
        };
        
        const searchResponse = await makeGithubRequest<GithubSearchResponse>(
            '/search/issues', 
            searchParams
        );
        
        if (!searchResponse) {
            return {
                content: [
                    {
                        type: "text",
                        text: "Failed to retrieve issues from GitHub"
                    }
                ]
            };
        }
        
        if (searchResponse.total_count === 0) {
            return {
                content: [
                    {
                        type: "text",
                        text: `No similar issues found in ${owner}/${repo}`
                    }
                ]
            };
        }
        
        // Calculate similarity score for each issue
        const issuesWithScores = searchResponse.items
            .map(issue => ({
                issue,
                score: calculateSimilarity(searchText, `${issue.title} ${issue.body || ''}`)
            }))
            .sort((a, b) => b.score - a.score)  // Sort by similarity score (highest first)
            .slice(0, maxResults);  // Take top N results
        
        // Format the response
        const formattedIssues = issuesWithScores.map(({ issue, score }) => 
            formatIssue(issue, score)
        );
        
        const responseText = `Found ${issuesWithScores.length} similar issues in ${owner}/${repo}:\n\n${formattedIssues.join("\n")}`;
        
        return {
            content: [
                {
                    type: "text",
                    text: responseText
                }
            ]
        };
    }
  • Zod input schema defining parameters: owner, repo, issueDescription, and maxResults for the tool.
    {
        owner: z.string().describe("GitHub repository owner/organization"),
        repo: z.string().describe("GitHub repository name"),
        issueDescription: z.string().describe("Description of the issue to find similar ones for"),
        maxResults: z.number().int().min(1).max(20).default(5).describe("Maximum number of similar issues to return")
    },
  • src/index.ts:105-188 (registration)
    Registration of the 'find-similar-issues' tool using McpServer.tool() with name, description, input schema, and inline handler.
    server.tool(
        "find-similar-issues",
        "Find GitHub issues similar to a new issue description",
        {
            owner: z.string().describe("GitHub repository owner/organization"),
            repo: z.string().describe("GitHub repository name"),
            issueDescription: z.string().describe("Description of the issue to find similar ones for"),
            maxResults: z.number().int().min(1).max(20).default(5).describe("Maximum number of similar issues to return")
        },
        async ({ owner, repo, issueDescription, maxResults }) => {
            // Combine title and description for better search results
            const searchText = `${issueDescription}`;
            
            // Extract important keywords for search (simple approach)
            const keywords = searchText
                .toLowerCase()
                .replace(/[^\w\s]/g, '')
                .split(/\s+/)
                .filter(word => word.length > 3)  // Filter out short words
                .filter(word => !['the', 'and', 'that', 'this', 'with'].includes(word))  // Filter common words
                .slice(0, 10)  // Limit number of keywords
                .join(' ');
                
            // Search for issues in the repository
            const searchParams = {
                q: `repo:${owner}/${repo} ${keywords}`,
                sort: 'updated',
                order: 'desc',
                per_page: '30'  // Get more results to filter by similarity
            };
            
            const searchResponse = await makeGithubRequest<GithubSearchResponse>(
                '/search/issues', 
                searchParams
            );
            
            if (!searchResponse) {
                return {
                    content: [
                        {
                            type: "text",
                            text: "Failed to retrieve issues from GitHub"
                        }
                    ]
                };
            }
            
            if (searchResponse.total_count === 0) {
                return {
                    content: [
                        {
                            type: "text",
                            text: `No similar issues found in ${owner}/${repo}`
                        }
                    ]
                };
            }
            
            // Calculate similarity score for each issue
            const issuesWithScores = searchResponse.items
                .map(issue => ({
                    issue,
                    score: calculateSimilarity(searchText, `${issue.title} ${issue.body || ''}`)
                }))
                .sort((a, b) => b.score - a.score)  // Sort by similarity score (highest first)
                .slice(0, maxResults);  // Take top N results
            
            // Format the response
            const formattedIssues = issuesWithScores.map(({ issue, score }) => 
                formatIssue(issue, score)
            );
            
            const responseText = `Found ${issuesWithScores.length} similar issues in ${owner}/${repo}:\n\n${formattedIssues.join("\n")}`;
            
            return {
                content: [
                    {
                        type: "text",
                        text: responseText
                    }
                ]
            };
        }
    );
  • Helper function to calculate Jaccard similarity score between issue description and candidate issues for ranking.
    function calculateSimilarity(text1: string, text2: string): number {
        // Convert to lowercase and remove special characters for comparison
        const normalize = (text: string) => text.toLowerCase().replace(/[^\w\s]/g, '');
        
        const words1 = new Set(normalize(text1).split(/\s+/));
        const words2 = new Set(normalize(text2).split(/\s+/));
        
        // Find intersection of words
        const intersection = new Set([...words1].filter(word => words2.has(word)));
        
        // Calculate Jaccard similarity coefficient
        const union = new Set([...words1, ...words2]);
        
        return intersection.size / union.size;
    }
  • Helper function for making authenticated GitHub API requests, used by the tool for searching issues.
    async function makeGithubRequest<T>(path: string, params: Record<string, string> = {}): Promise<T | null> {
        const url = new URL(path, GITHUB_API_BASE);
        
        // Add query parameters
        Object.entries(params).forEach(([key, value]) => {
            if (value) url.searchParams.append(key, value);
        });
        
        const headers: {
            "User-Agent": string;
            "Accept": string;
            "X-GitHub-Api-Version": string;
            Authorization?: string;
        } = {
            "User-Agent": USER_AGENT,
            "Accept": "application/vnd.github+json",
            "X-GitHub-Api-Version": "2022-11-28"
        };
        
        // Add authorization if token is available
        if (GITHUB_TOKEN) {
            headers["Authorization"] = `Bearer ${GITHUB_TOKEN}`;
        }
    
        try {
            const response = await fetch(url.toString(), { headers });
            if (!response.ok) {
                throw new Error(`GitHub API error! status: ${response.status}`);
            }
            return (await response.json()) as T;
        } catch (error) {
            console.error("Error making GitHub request:", error);
            return null;
        }
    }
Install 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/jake-mok-nelson/mcp-find-similar-github-issues'

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