Skip to main content
Glama

search_code

Search GitHub repositories for code using queries with filters like language, file path, or repository. Returns matching file paths and repository names.

Instructions

Search for code across GitHub repositories. Returns a concise list with file paths and repositories. Use 'get_file_contents' for full file content.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
qYesSearch query using GitHub code search syntax. Examples: 'addClass in:file language:js', 'repo:owner/name path:src/ extension:py', 'org:github extension:js', 'filename:test.py', 'user:octocat extension:rb', 'console.log path:/src/components', 'TODO in:file path:src/'
sortNoSort field ('indexed' only)
orderNoSort order
per_pageNoResults per page (default 10, max 100)
pageNoPage number (default 1)

Implementation Reference

  • The async handler function that executes the 'search_code' tool logic. It validates the query, calls the GitHub code search API, formats results, and returns them as text content.
    	async ({ q, sort, order, per_page, page }) => {
    		try {
    			const tokens = splitSearchQuery(q.trim())
    			const hasFilenameQualifier = tokens.some(token => /^filename:/i.test(token))
    			const hasSearchTerm = tokens.some(token => {
    				const normalized = token.toUpperCase()
    				if (normalized === "AND" || normalized === "OR" || normalized === "NOT") {
    					return false
    				}
    
    				return !isCodeSearchQualifier(token)
    			})
    
    			// GitHub code search requires at least one search term, unless the query is a filename-only search.
    			if (!hasSearchTerm && !hasFilenameQualifier) {
    				return {
    					content: [{
    						type: "text",
    						text: `GitHub code search needs at least one search term unless you are doing a filename-only search. Your query \`${q}\` only contains qualifiers.\n\nExamples:\n- \`handleClick repo:owner/name\`\n- \`addClass language:javascript\`\n- \`TODO path:src/\`\n- \`filename:test.py\`\n\nPlease retry with a search term, or use \`filename:\` for a filename-only search.`,
    					}],
    				}
    			}
    
    			const response = await octokit.rest.search.code({
    				q,
    				sort,
    				order,
    				per_page,
    				page,
    				mediaType: {
    					format: "text-match",
    				},
    			})
    
    			const totalCount = response.data.total_count
    
    			// Extract only essential information including text matches
    			const results = response.data.items.map(item => ({
    				repository: item.repository.full_name,
    				path: item.path,
    				url: item.html_url,
    				// Only include the first match fragment for conciseness
    				match: item.text_matches?.[0]?.fragment?.slice(0, 200) || null,
    			}))
    
    			if (results.length === 0) {
    				return {
    					content: [{
    						type: "text",
    						text: `No code results found for query: \`${q}\`\n\nTips:\n- GitHub only indexes the default branch\n- GitHub only indexes files under 384 KB in repositories with recent activity\n- Try adding or broadening qualifiers like \`repo:\`, \`org:\`, \`language:\`, or \`path:\`\n- Ensure the search term is specific enough to be indexed`,
    					}],
    				}
    			}
    
    			// Format as simple text
    			const text = results
    				.map(
    					(item, i) =>
    						`${i + 1}. **${item.repository}** / \`${item.path}\`${item.match ? `\n   \`\`\`\n   ${item.match.replace(/\n/g, " ").trim()}\n   \`\`\`` : ""}`,
    				)
    				.join("\n\n")
    
    			return {
    				content: [
    					{
    						type: "text",
    						text: `### Found ${totalCount} total code result(s), showing ${results.length}:\n\n${text}`,
    					},
    				],
    			}
    		} catch (e: any) {
    			const responseMessage =
    				e.response?.data?.message ||
    				e.response?.data?.error ||
    				e.message ||
    				String(e)
    			const errorDetails = Array.isArray(e.response?.data?.errors)
    				? e.response.data.errors
    						.map((error: any) => error.message || error.code || JSON.stringify(error))
    						.filter(Boolean)
    				: []
    
    			if (e.status === 401) {
    				return {
    					content: [{
    						type: "text",
    						text: "GitHub code search requires an authenticated GitHub token. Please retry with a valid token that can access the repositories you want to search.",
    					}],
    				}
    			}
    
    			if (e.status === 422) {
    				const detailText = errorDetails.length > 0 ? `\n\nDetails:\n- ${errorDetails.join("\n- ")}` : ""
    				return {
    					content: [{
    						type: "text",
    						text: `GitHub rejected the search query: ${responseMessage}${detailText}\n\nTry one of these fixes:\n- include at least one search term unless you are using \`filename:\`\n- verify the qualifier syntax for code search\n- if you used \`repo:\`, \`org:\`, or \`user:\`, confirm the token can access those resources`,
    					}],
    				}
    			}
    
    			return {
    				content: [{ type: "text", text: `Error: ${responseMessage}` }],
    			}
    		}
    	},
    )
  • Zod input validation schema for the 'search_code' tool, defining the 'q', 'sort', 'order', 'per_page', and 'page' parameters with descriptions and defaults.
    {
    	q: z
    		.string()
    		.describe(
    			"Search query using GitHub code search syntax. Examples: 'addClass in:file language:js', 'repo:owner/name path:src/ extension:py', 'org:github extension:js', 'filename:test.py', 'user:octocat extension:rb', 'console.log path:/src/components', 'TODO in:file path:src/'",
    		),
    	sort: z
    		.enum(["indexed"])
    		.optional()
    		.describe("Sort field ('indexed' only)"),
    	order: z.enum(["asc", "desc"]).optional().describe("Sort order"),
    	per_page: z
    		.number()
    		.optional()
    		.default(10)
    		.describe("Results per page (default 10, max 100)"),
    	page: z
    		.number()
    		.optional()
    		.default(1)
    		.describe("Page number (default 1)"),
    },
  • Registration of the 'search_code' tool via server.tool(), connecting the name, description, schema, and handler.
    // Tool: Search Code
    server.tool(
    	"search_code",
    	"Search for code across GitHub repositories. Returns a concise list with file paths and repositories. Use 'get_file_contents' for full file content.",
    	{
    		q: z
    			.string()
    			.describe(
    				"Search query using GitHub code search syntax. Examples: 'addClass in:file language:js', 'repo:owner/name path:src/ extension:py', 'org:github extension:js', 'filename:test.py', 'user:octocat extension:rb', 'console.log path:/src/components', 'TODO in:file path:src/'",
    			),
    		sort: z
    			.enum(["indexed"])
    			.optional()
    			.describe("Sort field ('indexed' only)"),
    		order: z.enum(["asc", "desc"]).optional().describe("Sort order"),
    		per_page: z
    			.number()
    			.optional()
    			.default(10)
    			.describe("Results per page (default 10, max 100)"),
    		page: z
    			.number()
    			.optional()
    			.default(1)
    			.describe("Page number (default 1)"),
    	},
    	async ({ q, sort, order, per_page, page }) => {
    		try {
    			const tokens = splitSearchQuery(q.trim())
    			const hasFilenameQualifier = tokens.some(token => /^filename:/i.test(token))
    			const hasSearchTerm = tokens.some(token => {
    				const normalized = token.toUpperCase()
    				if (normalized === "AND" || normalized === "OR" || normalized === "NOT") {
    					return false
    				}
    
    				return !isCodeSearchQualifier(token)
    			})
    
    			// GitHub code search requires at least one search term, unless the query is a filename-only search.
    			if (!hasSearchTerm && !hasFilenameQualifier) {
    				return {
    					content: [{
    						type: "text",
    						text: `GitHub code search needs at least one search term unless you are doing a filename-only search. Your query \`${q}\` only contains qualifiers.\n\nExamples:\n- \`handleClick repo:owner/name\`\n- \`addClass language:javascript\`\n- \`TODO path:src/\`\n- \`filename:test.py\`\n\nPlease retry with a search term, or use \`filename:\` for a filename-only search.`,
    					}],
    				}
    			}
    
    			const response = await octokit.rest.search.code({
    				q,
    				sort,
    				order,
    				per_page,
    				page,
    				mediaType: {
    					format: "text-match",
    				},
    			})
    
    			const totalCount = response.data.total_count
    
    			// Extract only essential information including text matches
    			const results = response.data.items.map(item => ({
    				repository: item.repository.full_name,
    				path: item.path,
    				url: item.html_url,
    				// Only include the first match fragment for conciseness
    				match: item.text_matches?.[0]?.fragment?.slice(0, 200) || null,
    			}))
    
    			if (results.length === 0) {
    				return {
    					content: [{
    						type: "text",
    						text: `No code results found for query: \`${q}\`\n\nTips:\n- GitHub only indexes the default branch\n- GitHub only indexes files under 384 KB in repositories with recent activity\n- Try adding or broadening qualifiers like \`repo:\`, \`org:\`, \`language:\`, or \`path:\`\n- Ensure the search term is specific enough to be indexed`,
    					}],
    				}
    			}
    
    			// Format as simple text
    			const text = results
    				.map(
    					(item, i) =>
    						`${i + 1}. **${item.repository}** / \`${item.path}\`${item.match ? `\n   \`\`\`\n   ${item.match.replace(/\n/g, " ").trim()}\n   \`\`\`` : ""}`,
    				)
    				.join("\n\n")
    
    			return {
    				content: [
    					{
    						type: "text",
    						text: `### Found ${totalCount} total code result(s), showing ${results.length}:\n\n${text}`,
    					},
    				],
    			}
    		} catch (e: any) {
    			const responseMessage =
    				e.response?.data?.message ||
    				e.response?.data?.error ||
    				e.message ||
    				String(e)
    			const errorDetails = Array.isArray(e.response?.data?.errors)
    				? e.response.data.errors
    						.map((error: any) => error.message || error.code || JSON.stringify(error))
    						.filter(Boolean)
    				: []
    
    			if (e.status === 401) {
    				return {
    					content: [{
    						type: "text",
    						text: "GitHub code search requires an authenticated GitHub token. Please retry with a valid token that can access the repositories you want to search.",
    					}],
    				}
    			}
    
    			if (e.status === 422) {
    				const detailText = errorDetails.length > 0 ? `\n\nDetails:\n- ${errorDetails.join("\n- ")}` : ""
    				return {
    					content: [{
    						type: "text",
    						text: `GitHub rejected the search query: ${responseMessage}${detailText}\n\nTry one of these fixes:\n- include at least one search term unless you are using \`filename:\`\n- verify the qualifier syntax for code search\n- if you used \`repo:\`, \`org:\`, or \`user:\`, confirm the token can access those resources`,
    					}],
    				}
    			}
    
    			return {
    				content: [{ type: "text", text: `Error: ${responseMessage}` }],
    			}
    		}
    	},
    )
  • src/index.ts:14-15 (registration)
    Registration is called from registerAllToolsAndResources which invokes registerSearchTools(server, octokit).
    export function registerAllToolsAndResources(server: McpServer, octokit: Octokit): void {
    	registerSearchTools(server, octokit)
  • Supporting utilities: CODE_SEARCH_QUALIFIERS set, splitSearchQuery function, and isCodeSearchQualifier function used by the handler to validate queries.
    const CODE_SEARCH_QUALIFIERS = new Set([
    	"repo",
    	"org",
    	"user",
    	"language",
    	"path",
    	"extension",
    	"filename",
    	"in",
    	"size",
    	"fork",
    ])
    
    function splitSearchQuery(query: string): string[] {
    	const tokens: string[] = []
    	let current = ""
    	let inQuotes = false
    
    	for (const char of query) {
    		if (char === "\"") {
    			inQuotes = !inQuotes
    			current += char
    			continue
    		}
    
    		if (/\s/.test(char) && !inQuotes) {
    			if (current) tokens.push(current)
    			current = ""
    			continue
    		}
    
    		current += char
    	}
    
    	if (current) tokens.push(current)
    
    	return tokens
    }
    
    function isCodeSearchQualifier(token: string): boolean {
    	const separatorIndex = token.indexOf(":")
    	if (separatorIndex <= 0) return false
    
    	return CODE_SEARCH_QUALIFIERS.has(token.slice(0, separatorIndex).toLowerCase())
    }
Behavior2/5

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

No annotations are provided, so the description carries full burden. It mentions 'concise list' but omits pagination behavior, rate limits, authentication needs, or what happens if no results. With zero annotation coverage, more behavioral disclosure is needed for a search tool.

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?

Two sentences achieve the purpose: first states what it does and output format, second provides a sibling cross-reference. No redundant information, perfectly front-loaded and concise.

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

Completeness4/5

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

For a straightforward search tool with full parameter descriptions and a sibling pointer, the description is mostly adequate. However, given no output schema and no annotations, additional context on pagination or result structure would improve completeness, but the current text covers the essential use case.

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 coverage is 100% as all 5 parameters have descriptions. The description adds no additional meaning beyond what the schema already provides. Baseline 3 is appropriate since description does not compensate for any gaps.

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

Purpose5/5

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

The description clearly states 'Search for code across GitHub repositories', using a specific verb and resource. It distinguishes itself from sibling search tools (search_issues, search_repositories, search_users) by targeting code search. Also mentions return format (concise list with file paths and repositories).

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

Usage Guidelines4/5

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

The description provides clear context: it returns a concise code search result. It explicitly points to get_file_contents for full file content, guiding when to use an alternative. However, it does not explicitly state when not to use this tool or compare with other search siblings, though the domain difference is implied.

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/hithereiamaliff/mcp-github'

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