lookup_github_repo
Evaluate the trustworthiness of any public GitHub repository by analyzing its behavioral commitment signals: project age, commit frequency, community size, release cadence, and social proof.
Instructions
Get a behavioral commitment profile for any public GitHub repository. Returns real signals: how long the project has existed, recent commit frequency, contributor community size, release cadence, and social proof. These are behavioral commitments — harder to fake than README claims.
Useful for: vetting open-source dependencies, evaluating AI tools/frameworks, assessing vendor reliability. Examples: "vercel/next.js", "facebook/react", "https://github.com/piiiico/proof-of-commitment"
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| repo | Yes | GitHub repository in "owner/repo" format or full URL. Example: "vercel/next.js" |
Implementation Reference
- src/mcp/server.ts:280-357 (handler)MCP tool handler for lookup_github_repo. Parses the 'repo' input via parseGitHubInput, calls buildGitHubCommitmentProfile, and returns the behavioral commitment profile (summary text + structured JSON breakdown). Handles errors for invalid input, missing repos, and unexpected exceptions.
// ── Tool: lookup_github_repo ── server.tool( "lookup_github_repo", `Get a behavioral commitment profile for any public GitHub repository. Returns real signals: how long the project has existed, recent commit frequency, contributor community size, release cadence, and social proof. These are behavioral commitments — harder to fake than README claims. Useful for: vetting open-source dependencies, evaluating AI tools/frameworks, assessing vendor reliability. Examples: "vercel/next.js", "facebook/react", "https://github.com/piiiico/proof-of-commitment"`, { repo: z .string() .describe( 'GitHub repository in "owner/repo" format or full URL. Example: "vercel/next.js"' ), }, async ({ repo }) => { const parsed = parseGitHubInput(repo); if (!parsed) { return { content: [ { type: "text" as const, text: `Invalid GitHub repo format. Use "owner/repo". Example: "vercel/next.js"`, }, ], isError: true, }; } try { const profile = await buildGitHubCommitmentProfile( parsed.owner, parsed.repo ); if (!profile) { return { content: [ { type: "text" as const, text: `Repository ${parsed.owner}/${parsed.repo} not found or not accessible.`, }, ], }; } return { content: [ { type: "text" as const, text: profile.summary }, { type: "text" as const, text: JSON.stringify( { fullName: profile.fullName, ageYears: Math.round(profile.ageYears * 10) / 10, stars: profile.stars, recentCommits30d: profile.recentCommits30d, contributorCount: profile.contributorCount, commitmentScore: profile.commitmentScore, scoreBreakdown: profile.scoreBreakdown, }, null, 2 ), }, ], }; } catch (err) { const message = err instanceof Error ? err.message : "Unknown error"; return { content: [ { type: "text" as const, text: `Error: ${message}` }, ], isError: true, }; } } ); - src/backend/github.ts:146-313 (helper)Core implementation of buildGitHubCommitmentProfile. Queries GitHub public API for repo metadata, recent commits (30d), contributor count, releases (up to 100), and OpenSSF Scorecard. Computes a weighted behavioral commitment score across 5 dimensions: longevity (30pts), recent activity (25pts), community (20pts), release cadence (15pts), and social proof (10pts). Applies 50% penalty for archived or inactive (>2yr) repos. Returns a structured profile with summary string.
export async function buildGitHubCommitmentProfile( owner: string, repo: string ): Promise<GitHubCommitmentProfile | null> { // 1. Repo metadata const repoRes = await ghFetch(`/repos/${owner}/${repo}`); if (!repoRes.ok) return null; const repoData = (await repoRes.json()) as RepoData; const now = Date.now(); const createdAt = new Date(repoData.created_at).getTime(); const pushedAt = new Date(repoData.pushed_at).getTime(); const ageYears = (now - createdAt) / (365.25 * 24 * 3600 * 1000); const daysSinceLastPush = (now - pushedAt) / (24 * 3600 * 1000); // 2. Recent commits (last 30 days) — single page, count what we get const since = new Date(now - 30 * 24 * 3600 * 1000).toISOString(); let recentCommits30d = 0; try { const commitRes = await ghFetch( `/repos/${owner}/${repo}/commits?since=${since}&per_page=100` ); if (commitRes.ok) { const commits = (await commitRes.json()) as Commit[]; recentCommits30d = commits.length; } } catch { // Non-fatal } // 3. Contributor count (first page — 30 by default) let contributorCount = 1; try { const contribRes = await ghFetch( `/repos/${owner}/${repo}/contributors?per_page=30&anon=false` ); if (contribRes.ok) { const contribs = (await contribRes.json()) as Contributor[]; contributorCount = Math.max(contribs.length, 1); // If we got a full page, there are likely more — indicate 30+ if (contribs.length === 30) contributorCount = 35; } } catch { // Non-fatal } // 4. Releases + Scorecard (concurrent) let releaseCount = 0; let latestRelease: string | null = null; let scorecardScore: number | null = null; let hasDangerousWorkflow: boolean | null = null; const [relResult, scorecardResult] = await Promise.allSettled([ (async () => { // per_page=100 is GitHub's max — gives accurate stable-release count up to // 100 (which exceeds our scoring threshold of >=10). per_page=10 truncates // and under-reports both releaseCount and the displayed value when many of // the latest 10 releases are prereleases (e.g. tj/commander.js: 8 stable // surface vs 67 actual). const relRes = await ghFetch( `/repos/${owner}/${repo}/releases?per_page=100` ); if (!relRes.ok) return { releaseCount: 0, latestRelease: null }; const releases = (await relRes.json()) as Release[]; const stable = releases.filter((r) => !r.prerelease); return { releaseCount: stable.length, latestRelease: stable[0]?.tag_name ?? null }; })(), fetchScorecardScore(owner, repo), ]); if (relResult.status === "fulfilled" && relResult.value) { releaseCount = relResult.value.releaseCount; latestRelease = relResult.value.latestRelease; } if (scorecardResult.status === "fulfilled" && scorecardResult.value) { scorecardScore = scorecardResult.value.score; hasDangerousWorkflow = scorecardResult.value.hasDangerousWorkflow; } // 5. Score const longevity = scoreAge(ageYears); const recentActivity = scoreActivity(recentCommits30d); const community = scoreCommunity(contributorCount); const releaseCadence = scoreReleases(releaseCount); const socialProof = scoreStars(repoData.stargazers_count); const commitmentScore = longevity + recentActivity + community + releaseCadence + socialProof; // 6. Penalty: archived or no activity in 2+ years const adjustedScore = repoData.archived || daysSinceLastPush > 730 ? Math.round(commitmentScore * 0.5) : commitmentScore; // 7. Summary const ageStr = ageYears >= 1 ? `${Math.floor(ageYears)} year${Math.floor(ageYears) > 1 ? "s" : ""}` : `${Math.round(ageYears * 12)} months`; const activityStr = repoData.archived ? "ARCHIVED" : daysSinceLastPush > 365 ? `inactive (last push ${Math.round(daysSinceLastPush / 30)} months ago)` : recentCommits30d > 0 ? `${recentCommits30d} commits in the last 30 days` : `no commits in the last 30 days (last push ${Math.round(daysSinceLastPush)} days ago)`; const scorecardStr = scorecardScore !== null ? `OpenSSF Scorecard: ${scorecardScore}/10${hasDangerousWorkflow ? " ⚠️ Dangerous workflow detected" : ""}` : null; const lines = [ `Repository: ${repoData.full_name}`, repoData.description ? `Description: ${repoData.description}` : null, `Age: ${ageStr}`, `Stars: ${repoData.stargazers_count.toLocaleString()} | Forks: ${repoData.forks_count}`, `Contributors: ${contributorCount === 35 ? "30+" : contributorCount}`, `Activity: ${activityStr}`, latestRelease ? `Latest release: ${latestRelease}` : "No stable releases", repoData.language ? `Primary language: ${repoData.language}` : null, repoData.license ? `License: ${repoData.license.spdx_id}` : "No license", repoData.archived ? "⚠️ Repository is archived (read-only)" : null, scorecardStr, ``, `Commitment Score: ${adjustedScore}/100`, ` Longevity: ${longevity}/30`, ` Recent activity: ${recentActivity}/25`, ` Community: ${community}/20`, ` Release cadence: ${releaseCadence}/15`, ` Social proof: ${socialProof}/10`, ] .filter(Boolean) .join("\n"); return { fullName: repoData.full_name, description: repoData.description, owner: repoData.owner.login, repo, language: repoData.language, license: repoData.license?.spdx_id ?? null, topics: repoData.topics, isArchived: repoData.archived, isFork: repoData.fork, ageYears, stars: repoData.stargazers_count, forks: repoData.forks_count, recentCommits30d, contributorCount, latestRelease, releaseCount, daysSinceLastPush: Math.round(daysSinceLastPush), scorecardScore, hasDangerousWorkflow, commitmentScore: adjustedScore, scoreBreakdown: { longevity, recentActivity, community, releaseCadence, socialProof, }, summary: lines, }; } - src/backend/github.ts:354-367 (helper)Utility function parseGitHubInput. Normalizes input (trim, strip github.com URL prefix, .git suffix, trailing slashes) then splits on '/' to extract owner and repo. Returns null if fewer than 2 parts.
export function parseGitHubInput(input: string): { owner: string; repo: string; } | null { const normalized = input .trim() .replace(/^https?:\/\/github\.com\//, "") .replace(/\.git$/, "") .replace(/\/$/, ""); const parts = normalized.split("/").filter(Boolean); if (parts.length < 2) return null; return { owner: parts[0]!, repo: parts[1]! }; } - src/backend/github.ts:57-93 (schema)Type definition for GitHubCommitmentProfile including all behavioral signals (ageYears, stars, forks, recentCommits30d, contributorCount, releaseCount, daysSinceLastPush), scorecard data, commitmentScore, scoreBreakdown, and summary string.
export interface GitHubCommitmentProfile { fullName: string; description: string | null; owner: string; repo: string; language: string | null; license: string | null; topics: string[]; isArchived: boolean; isFork: boolean; // Behavioral signals ageYears: number; stars: number; forks: number; recentCommits30d: number; contributorCount: number; latestRelease: string | null; releaseCount: number; daysSinceLastPush: number; // Build/process integrity (OpenSSF Scorecard) scorecardScore: number | null; // 0–10 overall Scorecard score (null = not available) hasDangerousWorkflow: boolean | null; // true = Dangerous-Workflow check failed (score 0) // Score commitmentScore: number; scoreBreakdown: { longevity: number; recentActivity: number; community: number; releaseCadence: number; socialProof: number; }; summary: string; } - src/mcp/server.ts:288-294 (registration)Zod schema for the 'repo' input parameter of the lookup_github_repo tool — expects a string in 'owner/repo' format or full GitHub URL.
{ repo: z .string() .describe( 'GitHub repository in "owner/repo" format or full URL. Example: "vercel/next.js"' ), },