search_repos
Find GitHub repositories by keyword or topic to identify competitors, similar tools, or related projects with activity signals and star rankings.
Instructions
Search GitHub for repositories matching a keyword or topic. Returns top results by stars with activity signals. Use to find competitors, similar tools, or related projects.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Search query e.g. 'mcp server typescript' or 'cashflow prediction python' | |
| max_length | No |
Implementation Reference
- src/server.ts:121-130 (registration)Registration of the 'search_repos' tool in server.ts.
server.registerTool( "search_repos", { description: "Search GitHub for repositories matching a keyword or topic. Returns top results by stars with activity signals. Use to find competitors, similar tools, or related projects.", inputSchema: z.object({ query: z.string().describe("Search query e.g. 'mcp server typescript' or 'cashflow prediction python'"), max_length: z.number().optional().default(6000), }), annotations: { readOnlyHint: true, openWorldHint: true }, - src/server.ts:132-140 (handler)The tool handler function in server.ts which calls repoSearchAdapter.
async ({ query, max_length }) => { try { const result = await repoSearchAdapter({ url: query, maxLength: max_length }); const ctx = stampFreshness(result, { url: query, maxLength: max_length }, "github_search"); return { content: [{ type: "text", text: formatForLLM(ctx) }] }; } catch (err) { return { content: [{ type: "text", text: formatSecurityError(err) }] }; } } - src/adapters/repoSearch.ts:5-79 (handler)The actual implementation of the repoSearch logic.
export async function repoSearchAdapter(options: ExtractOptions): Promise<AdapterResult> { // Sanitize query input const query_input = sanitizeQuery(options.url); let query = query_input; // If it's a full URL, extract the query param try { const parsed = new URL(options.url); if (parsed.hostname === "github.com" && parsed.pathname.includes("/search")) { query = parsed.searchParams.get("q") ?? options.url; } else if (parsed.hostname === "github.com") { // It's a direct URL — not a search query = parsed.pathname.replace("/search", "").trim().replace(/^\//, ""); } } catch { // plain string query, use as-is } const apiUrl = `https://api.github.com/search/repositories?q=${encodeURIComponent(query)}&sort=stars&order=desc&per_page=10`; const res = await fetch(apiUrl, { headers: { Accept: "application/vnd.github.v3+json", "User-Agent": "freshcontext-mcp/0.1.0", }, }); if (!res.ok) { throw new Error(`GitHub Search API error: ${res.status} ${await res.text()}`); } const data = await res.json() as { total_count: number; items: Array<{ full_name: string; description: string | null; html_url: string; stargazers_count: number; forks_count: number; language: string | null; topics: string[]; pushed_at: string; created_at: string; open_issues_count: number; }>; }; const raw = [ `Total matching repos: ${data.total_count.toLocaleString()}`, `Top ${data.items.length} by stars:\n`, ...data.items.map((r, i) => [ `[${i + 1}] ${r.full_name}`, `⭐ ${r.stargazers_count.toLocaleString()} stars | 🍴 ${r.forks_count} forks | Issues: ${r.open_issues_count}`, `Language: ${r.language ?? "unknown"}`, `Topics: ${r.topics?.join(", ") || "none"}`, `Description: ${r.description ?? "N/A"}`, `Last push: ${r.pushed_at}`, `Created: ${r.created_at}`, `URL: ${r.html_url}`, ].join("\n") ), ] .join("\n\n") .slice(0, options.maxLength ?? 6000); // Most recently pushed repo date as content_date const dates = data.items.map((r) => r.pushed_at).sort().reverse(); return { raw, content_date: dates[0] ?? null, freshness_confidence: "high", }; }