Skip to content

About

MCP server that checks whether your idea already exists on GitHub before you build it. Ranking weights are configurable.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

search-first

Before you build this, here is what already exists on GitHub.

An MCP server that discovers existing public projects, explains a ranked shortlist, inspects maintenance and reuse signals, and compares candidates. Search first, inspect and test a candidate, then use it or fork and adapt it. Build from scratch only after a broader search fails to find a suitable base.

Version 0.1, MIT licensed. It is not yet published as a package or a hosted service, and the project and distribution (search-first-mcp) names may still change. The source URL below supplies public main. For an unpublished update, use the supplied checkout or clone its bundle with git clone -b BRANCH BUNDLE search-first.

Use a GitHub token for regular use. Without one, the 10 search requests/minute per public IP support at most 5 bounded idea searches/minute at two variants each; space realistic searches at least 60 seconds apart on a shared IP. Authentication raises repository search to 30 requests/minute.

Create a read-only fine-grained token in three steps:

  1. Open GitHub Settings → Developer settings → Personal access tokens → Fine-grained tokens → Generate new token.
  2. Name it, set an expiration, select your account as resource owner and Public repositories as repository access. Leave optional permissions unset: tokens already include read-only access to public repositories.
  3. Generate it and supply it as GITHUB_TOKEN through your secret manager or the environment of the process launching the server. Keep it out of tracked files.

See GitHub's authentication guide. --help and fixture tests work without authentication.

Install and run

The minimum is Python 3.11 (the server uses Python 3.11's asyncio.timeout and datetime.UTC). Git is needed to clone the source. The recommended route is uvx, which can download a supported Python without changing your system Python or needing administrator access. Install uv using its official installer:

  • Linux (including Debian/Ubuntu): curl -LsSf https://astral.sh/uv/install.sh | sh
  • macOS: curl -LsSf https://astral.sh/uv/install.sh | sh
  • Windows PowerShell: powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

The Unix installer defaults to ~/.local/bin. On shared or sandboxed machines, choose your own folder: curl -LsSf https://astral.sh/uv/install.sh | UV_INSTALL_DIR="$PWD/uv-bin" UV_NO_MODIFY_PATH=1 sh.

Open a new terminal so uv and uvx are on PATH. Until a package is published, clone the source and run it locally (these commands also work in PowerShell):

git clone -b main https://github.com/TayHCode/search-first.git
cd search-first
uvx --python 3.12 --from . search-first --help
uvx --python 3.12 --from . search-first

The help command verifies installation without GitHub credentials. The next command starts stdio: a server waiting silently for an MCP client is normal. Use Ctrl+C to exit. Stdout is reserved for MCP messages. For local HTTP instead:

uvx --python 3.12 --from . search-first --transport streamable-http

The endpoint is http://127.0.0.1:8000/mcp. Connect an MCP client, then stop the server with Ctrl+C when finished.

Alternative: pip in a virtual environment

Check the interpreter before installing dependencies. The preflight prints search-first needs Python 3.11 or newer; you have X on an old interpreter, rather than pip's misleading dependency error from versions: none.

Linux/macOS, from the clone:

python3 scripts/check_python.py
python3 -m venv .venv
. .venv/bin/activate
python -m pip install .
search-first --help

Stop if the preflight fails. Stock macOS /usr/bin/python3 may be 3.9.6: use the uvx route above, or install Python 3.11+ from python.org and run the preflight with that interpreter. On Debian/Ubuntu, missing ensurepip means you need the matching python3-venv package (sudo apt install python3-venv with administrator access); the uvx route works without sudo. For a uv-created venv use uv venv --python 3.12 followed by uv pip install --python .venv .; uv venv alone does not install pip.

Windows PowerShell, with Python 3.11 installed from python.org:

py -3.11 scripts/check_python.py
py -3.11 -m venv .venv
.\.venv\Scripts\python.exe -m pip install .
.\.venv\Scripts\search-first.exe --help

Use py -3.12 if 3.12 is installed instead. Direct executable paths avoid changing PowerShell's activation-script policy. To run development checks, install '.[dev]' in place of . (quote it in PowerShell too); see "Proof and development" below.

The server does not call a model or require model credentials. Your calling agent provides query variants, judges functional fit, and controls any subsequent work.

GitHub limits and authentication

The server reads GITHUB_TOKEN, then GH_TOKEN, from its process environment. If neither is set, --use-gh-token explicitly opts in to gh auth token; the CLI fallback is off by default. Credentials are never included in tool responses, logs or cache files. Prefer inherited environment or a secret manager; keep any credential-bearing client configuration untracked. Empty values, unexpanded ${VAR}, $VAR or %VAR% references and example values such as your-token-here are ignored. Every unauthenticated response includes a notes entry explaining the two-variant budget and saying set GITHUB_TOKEN for complete searches. Ignored placeholders additionally produce token placeholder not expanded; running unauthenticated in warnings. A real but invalid credential still returns an authentication error; it is not silently ignored.

GitHub's repository search limits are about 10 requests/minute per IP without authentication and 30/minute with authentication. Other users sharing an IP or account spend the same budget. Core REST limits are 60/hour per IP unauthenticated and generally 5,000/hour per authenticated user; GitHub Actions tokens and some app/enterprise credentials have different limits. Secondary limits can also apply.

Request budgets: unauthenticated search executes at most 2 variants per call; authenticated search executes at most 6. The first supplied variants take priority. Known remaining quota can reduce the plan further. Extra planned variants are skipped deliberately to conserve quota; coverage reports completed, failed, skipped and the selected query budget. detail=true also returns each query and its status. search_complete means all selected variants completed, without a GitHub incomplete-results flag; it never means an exhaustive search of every variant or all of GitHub. Set GITHUB_TOKEN to search more variants. Space unauthenticated idea calls at least 60 seconds apart, especially when other machines share your IP.

Cache hits are free. One uncached variant consumes one search request. Per-call network budgets are 6 requests without a token, 36 with one, including optional reads. inspect_repo uses up to 6 core requests and compare_candidates up to 6 per unique repo; an unauthenticated comparison may be partial. Core quotas are distinct from search quotas. Six rapid searches can exhaust a shared search budget; these estimates are not guarantees.

Uncached search requests are paced at least 6.1 seconds apart without a token, or 2.1 seconds with one. Quota/reset and Retry-After headers trigger cooldown. When at least one query completed, failures return partial results and retry_after_seconds. Zero completed queries return an MCP tool error starting No search completed: followed by the cause. A connection refusal, unreachable network, DNS failure or connection timeout says GitHub unreachable from this machine: check network/proxy and stops the plan; the connection timeout is 2.5 seconds. Rate limiting says rate limited; retry in N s; a token raises the limit. A connected but slow server instead reports a request timeout. Transport details and proxy credentials are never returned.

The total deadline is 30 seconds unauthenticated, 15 seconds with a token; it includes queueing, pacing and all reads. Individual response reads are capped at 20 seconds. Override it with --call-timeout SECONDS (1–120). Timeout, failed queries and GitHub-incomplete results always yield inconclusive, even with an excellent match. Retry before choosing to use, fork, borrow or build. A vague idea can still return a zero-completed error during cooldown. More than 30 matches per query means the bounded sample is truncated, not that an executed query failed.

Token creation is described above. For Claude Code, use the explicit -e command in the client section or inherit GITHUB_TOKEN from the launching environment. For JSON-based clients, an env block must contain the actual value, supplied locally, or omit GITHUB_TOKEN entirely. Do not paste ${GITHUB_TOKEN} into a generic JSON config: expansion is client-specific, and Claude Code leaves an unset reference literal. The headless example below has no env block; it works without a token, or with a token inherited by the client. GUI clients often need an explicit local value because they do not inherit your terminal environment.

Only public repositories are returned; inspecting private metadata is refused. GitHub receives your search terms. Do not include confidential requirements or secrets. Cache defaults to $XDG_CACHE_HOME/search-first (or ~/.cache/search-first) for five minutes; --cache-dir PATH changes it and --cache-ttl 0 disables it. Queries already cached within that TTL can answer offline, on Windows only within the same running server process (its cache is memory-only). Uncached endpoints still need network access. Windows lacks the POSIX ownership/mode checks used for the disk cache. The cache directory is created with mode 0700 and cache files are written 0600; an existing directory that you do not own, or that others can write to, is not used. Cache entries contain public response data, not authentication headers. Known credentials and recognizable GitHub tokens are scrubbed, including decoded README text. Expired entries are pruned at startup and replaced when reused.

Tools

search_existing

{
  "idea": "a Chrome extension or script that closes idle browser tabs automatically",
  "queries": [
    "chrome idle tab close", "auto close tabs",
    "inactive tabs extension", "tab auto closer"
  ],
  "language": null,
  "min_stars": 0,
  "include_archived": false,
  "limit": 8,
  "sort_by": "relevance"
}

The idea must contain 1–2000 characters and name concrete behavior and a domain. Vague aspirations such as "something to make my life easier" yield inconclusive.

Pass 2 to 6 short query variants: synonyms, different framings, ecosystem terms. Queries are plain terms, optionally quoted, not GitHub qualifiers. The server always adds one idea-derived variant (the first four meaningful words of the idea). If you pass fewer than two variants it adds up to three more, including a repository-topic search. It queries the GitHub REST repository search API, retrieves up to 30 best matches per query, filters and deduplicates them, and returns at most limit rows (1–50). language filters the primary GitHub language, which can exclude mixed-language projects. Archived projects are excluded unless explicitly requested. sort_by orders the returned rows: relevance (default), stars, or recent_activity (newest push first). Implausible matches always sort last, whatever sort_by is, so stars orders only the plausible rows by stars and then lists the implausible ones. The verdict always uses relevance order: closest_match, when present, is the first row of the relevance shortlist, including its license/archive caveats. With stars or recent_activity, that same match may appear later. Missing key terms or hard constraints prevent use as is and fork and adapt. Both also require at least 25 stars and a known push within 365 days. A tiny or stale full match can remain an inspection lead with inconclusive, not an automatic fork verdict. Core coverage and these floors take priority over the weighted score in relevance ordering, so a maintained dedicated library outranks a tiny CLI with generic overlap.

The default response returns the top 8 candidates with name, URL, stars, last push, license, relevance, idea-term coverage, missing core terms/constraints and a one-line reason. It also includes the provisional verdict, completion counts, errors and token/quota notes. Typical default responses target under 8,000 characters. Set detail=true for descriptions, topics, ranking evidence, weights and query plans; limit (1–50) controls returned rows. Every free-text field is bounded: descriptions at 300 characters, topics at 10 entries (100 characters each), README excerpts and reasoning at 500 characters, other text at 300. verdict includes closest_match, plausible counts, search_complete, reasoning and one of use as is, fork and adapt, borrow parts, build from scratch or inconclusive. Counts describe the retrieved sample. Inspect and test candidates.

An abbreviated real recorded result, not invented example data:

{
  "verdict": {
    "closest_match": {
      "name": "maverick-360/vaultab",
      "url": "https://github.com/maverick-360/vaultab"
    },
    "plausible_candidates": 72,
    "recommendation": "fork and adapt",
    "provisional": true
  },
  "candidate": {
    "name": "maverick-360/vaultab",
    "stars": 5,
    "forks": 0,
    "license": "MIT",
    "language": "JavaScript",
    "relevance_score": 0.871,
    "days_since_push": 94,
    "why_it_matched": "Idea terms: browser, close, extension, idle, tab; returned by 4 query variant(s), best GitHub rank 1."
  }
}

This excerpt omits fields and uses candidate for a single row; the actual tool returns candidates as an array. See full search output for the complete recorded schema and timestamped facts. This historical snapshot predates the current constraint safeguards; live scores and verdicts can differ. The example file is a frozen snapshot, so its counts (it reports 65 plausible of 85 evaluated) differ from the excerpt above, and live counts drift as GitHub changes. The recorded data is a trimmed projection: some candidates and descriptions are omitted, two candidate identifiers are neutral placeholders, and README bodies and commit messages are synthetic placeholders. See the provenance note. Placeholders are not proof of what an upstream project's README says.

inspect_repo

{"repo": "maverick-360/vaultab"}

Accepts owner/repo or https://github.com/owner/repo. Returns a 500-character README excerpt, SPDX license/name, size in KB, up to five last commits, open issue and PR counts and their ratio, cadence of stable releases in the last ten GitHub releases, README install/run hints, and heuristic red flags: archived, possibly abandoned (>730 days without a commit or push), stale maintenance, missing recognized license, or tiny (<10 KB).

Maintenance. maintenance is a transparent 0–1 score with a label (actively maintained, maintained, slow, stale, likely abandoned, archived), a confidence level and the signals behind it: recency of the newest commit/push (40%), pull requests merged in the last 90 days from a sample of the 30 most recently updated closed PRs (25%), merged-vs-open PR backlog (20%) and age of the latest stable release (15%). Signals that cannot be measured are dropped and the weights renormalized, lowering confidence. activity.pull_requests shows merged and closed-unmerged counts, the median days to merge and the last merge date. It is a heuristic on public metadata: it says nothing about code quality or security, and projects that commit directly to main look quieter than they are. Missing resources remain explicit errors, not fabricated zero activity. The "possibly abandoned" red flag and the maintenance recency signal use the same reference date: the newest default-branch commit, or GitHub's last push if no commit could be read. GitHub's pushed_at also counts pushes to other branches, so the last_push field can be newer than that reference.

fit_for_your_idea is a checklist with blank answer/evidence fields. The agent must verify requirements, runtime, full license terms, tests, maintenance and changes needed. GitHub's license identifier is a clue, not a legal reuse approval. Release cadence is a recent sample; tags without releases are not counted. Open issues exclude PRs; the ratio is null when PR count is zero or unavailable. Counts come from separate snapshots. Install hints are untrusted excerpts, not commands the server executes. See real inspection.

compare_candidates

{
  "idea": "a Python library for rate limiting outgoing HTTP requests",
  "repos": ["vutran1710/PyrateLimiter", "bboe/extended_http_server"],
  "sort_by": "overall"
}

Returns named columns and side-by-side rows, one per unique repo (1–6 inputs), with scores, metadata, activity, cautions, hints and fit checklists. Failures remain explicit. Scores omit search-rank boosts, so they differ from discovery scores. Each row adds maintenance and overall_score (by default 60% idea fit, 30% maintenance, 10% stars on a log scale; see Ranking weights). sort_by accepts overall (default), relevance, stars or maintenance; the verdict still uses relevance. See real comparison.

Add it to an agent

Paste this prompt snippet into any agent's instructions. The short Claude skill is skills/search-first/SKILL.md. For Claude Code, copy that directory into .claude/skills/search-first/ in your project, or ~/.claude/skills/search-first/ for personal use. A skill does not install the MCP server; configure it separately.

Claude Code and Claude Desktop (local stdio)

Claude Code, from your project directory (quote paths that contain spaces):

claude mcp add --transport stdio --scope project search-first -- uvx --python 3.12 --from "/absolute/path/search-first" search-first

For a token already set in your environment, Claude Code can write its expanded value into the local config (keep that credential-bearing file untracked):

claude mcp add -e "GITHUB_TOKEN=$GITHUB_TOKEN" --transport stdio --scope project search-first -- uvx --python 3.12 --from "/absolute/path/search-first" search-first

PowerShell uses -e "GITHUB_TOKEN=$env:GITHUB_TOKEN" instead. Run one registration command, not both. With no token, use the first command without -e; omit the token key rather than inserting an environment reference or example value.

This writes .mcp.json in that project; Claude Code may include an empty "env": {} even when no -e was passed. Project-scoped servers need approval: start interactive claude in the project, approve the server when prompted, and use /mcp to inspect its status. Server approval and permission to call tools are separate. A healthy server alone does not grant headless tool permission.

For a headless session, save this server JSON as search-first.mcp.json with your actual absolute paths. It deliberately omits env and inherits the client's environment. If you have no token, leave it as written; no token key is required. For a client that cannot inherit environment, add "env": {"GITHUB_TOKEN": "REPLACE_WITH_YOUR_TOKEN"} to the server object and replace that example value with your real token locally. Keep that file untracked; JSON is not a shell.

{
  "mcpServers": {
    "search-first": {
      "command": "uvx",
      "args": ["--python", "3.12", "--from", "/absolute/path/search-first", "search-first"]
    }
  }
}

Use the absolute path to uvx if the client cannot find it on PATH. Then explicitly allow these read-only tools:

claude -p "Call search_existing for a terminal tool to close idle chrome tabs, using variants chrome idle tabs and CLI close inactive tabs. Report missing constraints." --mcp-config search-first.mcp.json --strict-mcp-config --allowedTools "mcp__search-first__search_existing" "mcp__search-first__inspect_repo" "mcp__search-first__compare_candidates"

The same command works in PowerShell. Alternatively, put the explicit permission rules in your project's .claude/settings.local.json:

{
  "permissions": {
    "allow": [
      "mcp__search-first__search_existing",
      "mcp__search-first__inspect_repo",
      "mcp__search-first__compare_candidates"
    ]
  }
}

Headless sessions cannot answer an approval prompt. --mcp-config selects the server configuration; --allowedTools grants the tool permissions. Review both before running. See the official MCP guide and CLI permission flags.

For Claude Desktop, use the same mcpServers object in claude_desktop_config.json. A macOS example (replace the path components):

{
  "mcpServers": {
    "search-first": {
      "command": "/Users/your-name/.local/bin/uvx",
      "args": ["--python", "3.12", "--from", "/Users/your-name/projects/search-first", "search-first"]
    }
  }
}

Windows JSON uses escaped backslashes (or forward slashes). Array elements are already arguments: do not add literal shell quotes around paths, even with spaces.

{
  "mcpServers": {
    "search-first": {
      "command": "C:\\Users\\your-name\\.local\\bin\\uvx.exe",
      "args": ["--python", "3.12", "--from", "C:\\Users\\your-name\\projects\\search-first", "search-first"]
    }
  }
}

Use the absolute executable path because a GUI may not inherit your shell PATH. Restart Desktop and ask it to list the three tools, then try the idle-tabs example.

Claude and ChatGPT connectors (remote Streamable HTTP)

The endpoint is /mcp. The server defaults to loopback. Set SEARCH_FIRST_AUTH_TOKEN (16+ characters) in the server's environment to require Authorization: Bearer <token> on every request; the server refuses a non-loopback --host without it unless you pass --allow-unauthenticated (only behind a trusted gateway). This shared secret suits programmatic clients such as your own agent harness, the Claude API MCP connector, or the OpenAI Responses API MCP tool, which can send an authorization header. It is not OAuth: the claude.ai and ChatGPT connector UIs expect OAuth or no auth, so they need a gateway that provides it. GitHub credentials belong on the server, never in client request headers.

A future HTTPS reverse proxy can forward to:

search-first --transport streamable-http \
  --allowed-host search.example.org \
  --allowed-origin https://search.example.org

Add each actual forwarded Host/Origin explicitly; DNS rebinding protection remains on. This is configuration guidance, not a deployed endpoint. A gateway must provide appropriate access controls and quota protection; connector-compatible OAuth is not included. For local development on either client, use an HTTPS forwarding endpoint you control. No tunnel or hosting account was created for this MVP.

Claude: In Customize → Connectors, choose Add → Add custom connector, enter https://your-host/mcp, review authentication, then enable it in a conversation. Use No sign in only for an endpoint intentionally configured for anonymous access. Remote connections originate from Claude's cloud, so localhost/VPN-only addresses will not work. Organization policies can restrict setup. See the official Claude connector guide.

ChatGPT: Where your account/workspace permits custom MCP servers, open Plugins, choose the plus button → Add custom MCP server, provide a name, description and HTTPS /mcp URL, configure authentication and review discovered tools. Some accounts still expose this through Settings → Apps → Advanced settings → Developer mode → Create. If available, Secure MCP Tunnel can connect a private endpoint instead; this MVP has not configured one. Start a chat and select the connector/plugin. Access and labels depend on your account and admin policies. See OpenAI's official connection guide.

Test status: Official SDK initialization, listing and calls are tested in-process, over stdio, and over Streamable HTTP on localhost/ASGI. Installation and live stdio search/inspection/comparison were verified on Linux, macOS M1 and Windows; stock macOS Python 3.9.6 uses the uv route. Claude Code project registration and headless read-only calls with explicit permissions were verified. Interactive project approval, Claude Desktop, claude.ai and ChatGPT account-level connector flows remain unverified. Remote HTTPS, ingress authentication and OAuth are not tested or deployed. Protocol compatibility is not a claim that every client's connector UI will accept this setup.

How ranking works (and where it fails)

Python was chosen over TypeScript for a small, inspectable server, built-in packaging, fixture tests, and easy uvx/pip use. TypeScript would also support MCP; there is no browser UI or JS runtime requirement here. We reuse the official MCP Python SDK (mcp 2.x), HTTPX and GitHub REST APIs, rather than implement protocol transports.

GitHub's official MCP server already provides raw repository search and many other tools. Use that if raw GitHub access is enough. This project adds bounded multi-variant discovery, explainable ranking, maintenance checks and an explicit reuse-before-build workflow. No code is copied from that server. The SDK's real transports are exercised by the protocol tests; upstream servers and candidates have not been installed or functionally tested.

By default, score = 50% normalized core-term coverage + 17% best core query-term coverage + 8% best GitHub result rank + 4% agreement across variants + 5% freshness + 16% logarithmic stars (the weights are settable, see below). Terms come from name, description, topics and primary language. A small suffix/synonym vocabulary equates limit/limiter/limiting, idle/inactive, docs/documentation and generate/generator. Generic wording (tool, HTTP, requests, Python, terminal) is not a missing core term. Ecosystem qualifiers still matter as explicit constraints: terminal/CLI, requested language, operating system, Chrome/Chromium and self-hosted must have metadata evidence. A homelab idea also flags Unraid/Proxmox/TrueNAS-only wording as unverified general scope. These small rules do not infer behavior from a README.

A plausible reusable base needs a specific idea, all core terms, no missing hard constraints, at least 25 stars, a known push within 365 days and score ≥0.55. Missing core terms cap the score below 0.8; missing constraints cap it below 0.55. Full, credible matches sort before tiny matches and partial matches, regardless of raw weighted score. core_terms, missing_key_terms, missing_constraints, reuse_floor_met and the one-line why_it_matched explain the decision. All reuse verdicts remain provisional; recent pushes and stars are weak maintenance signals. Within equal relevance scores and fit groups, ties resolve by metadata maintenance score (a low-confidence push-age heuristic), stars, normalized idea-term coverage in name/description/topics, push recency, then repository name. tie_break and why_it_matched say tied with N others and why the winner won, counting the full retrieved group before the result limit. CLI/terminal or command-line wording in descriptions or topics verifies an interface constraint; Python alone does not. If every candidate in the top relevance group lacks that evidence, the verdict says none of these is a terminal tool according to retrieved metadata; inspect before inferring support. Generic aspirations ("life easier", "computer faster") yield inconclusive and omit unrelated candidate rows, even with popular hits. The server plans one derived whole-idea variant; the request budget decides whether it runs. If you pass two or more variants it skips extra derived variants to save search quota.

Candidates below that threshold with at least two matched idea terms, 25% coverage and score ≥0.3 are counted as adjacent. If none is plausible but an adjacent candidate exists, the verdict is inconclusive and includes the closest adjacent match for inspection. This avoids saying “build from scratch” when, for example, a 342-star near-match misses features in a longer idea. Stars alone do not qualify a candidate. This regression uses a synthetic case; it is not a new live search. The agent supplies richer domain-specific synonyms. Derived variants are keyword slices plus a topic, not LLM-generated interpretations.

Ranking weights

The weights are server-start settings, not tool parameters, so an agent calling the tools cannot change them mid-session. Each has an environment variable and a matching flag; the flag wins. Format: name=value,name=value. Unnamed weights keep their defaults, then all weights are scaled to sum to 1, so stars=0.5 means "stars count for about 37% of the score". A flag replaces the matching environment variable completely; the two are not merged, so unnamed weights return to their defaults rather than to the environment value. Values must be numbers from 0 to 1000000 and not all zero; unknown names, repeated names, bad numbers and negatives stop the server at startup with an error that lists the allowed names.

Setting Names (default)
SEARCH_FIRST_RANK_WEIGHTS / --rank-weights idea_match (0.50), query_match (0.17), github_rank (0.08), variant_agreement (0.04), freshness (0.05), stars (0.16)
SEARCH_FIRST_OVERALL_WEIGHTS / --overall-weights fit (0.6), maintenance (0.3), stars (0.1)

The rank weights drive relevance_score in both tools. In compare_candidates, which has no search-rank evidence, only idea_match, freshness and stars count and are rescaled by their sum (so a perfect repo can still reach 1.0). The overall weights blend relevance_score, maintenance (0.5 when unknown) and stars into overall_score. Language, archived, license and resource-list penalties are fixed. The cache stores raw public results; weights apply at response time, so a cache hit can be reranked without another GitHub request. Detailed search and comparison responses carry a weights object with the weights actually in use. A signal with at least 90% of the normalized weight also adds a clear response warning. For example stars=1000000 nearly removes all fit signals and can produce an unrelated order; it remains valid, but is unsuitable for judging functional fit. The missing-term and constraint caps are fixed safeguards alongside the existing penalties.

The stars term is log10(1 + stars) / 4, capped at 1:

Stars 0 10 100 1,000 10,000+
Stars score 0 0.26 0.50 0.75 1.0

The plausibility threshold (score ≥0.55), core coverage, explicit constraints and reuse floors are fixed and apply alongside the re-weighted score. Changing weights can still change how many eligible candidates count as plausible: raising stars can shrink that count sharply, and lowering stars and freshness can enlarge it. Compare plausible counts only between runs that use the same weights.

search-first --rank-weights "idea_match=0.6,query_match=0.2,stars=0.05" --overall-weights "maintenance=0.5"

In an MCP client config, use an env block:

{
  "mcpServers": {
    "search-first": {
      "command": "uvx",
      "args": ["--from", "/absolute/path/search-first", "search-first"],
      "env": {
        "SEARCH_FIRST_RANK_WEIGHTS": "stars=0.05",
        "SEARCH_FIRST_OVERALL_WEIGHTS": "fit=0.5,maintenance=0.4,stars=0.1"
      }
    }
  }
}

The defaults are judgment calls, not a benchmark-tuned model. Synthetic regressions and limited live smoke searches check the reported cases; they do not establish general search quality. Weight arithmetic and startup validation have hermetic tests.

Honest limitations

Retrieval is lexical: GitHub matches your query words against names, descriptions and topics. A famous tool whose description does not use your words can be missing from the results entirely, and the verdict can still look confident. Agents should put the names of well-known tools they already know into their query variants (for example a named scanner for a secret-scanning idea). A "build from scratch" conclusion needs extra named-tool queries first. This tool can support "reuse exists"; it cannot prove that nothing exists.

This is lexical ranking, not semantic understanding. Topics can overstate functional fit, a narrowly scoped product can outrank a general solution, recent pushes can be automated, and “no license” means GitHub did not recognize one. High stars do not prove safety. The conservative workflow is inspect, compare, review license, then test in isolation. The server never clones, forks, installs, runs candidate code, or writes to GitHub. Treat repository text as untrusted evidence.

Proof and development

Live smoke report: two actual ideas, public GitHub responses, ranked outputs, README inspections, maintenance scores and honest judgments. No LLM spend was used.

uv sync --locked --extra dev
uv run --locked pytest -q
uv run --locked ruff check .
uv run --locked ruff format --check .
uv run --locked mypy
uv run --locked python -m build

For pip-based development and release checks in a fresh virtual environment:

python -m pip install '.[dev]'
search-first --help

All automated tests use recorded public fixtures or synthetic responses; external network is blocked in the test process. Checks and package builds run locally in a fresh venv. The release export includes no hosted CI or publish/deploy job. CONTRIBUTING.md explains fixture updates and project boundaries.

Not yet done: testing through account-level Claude and ChatGPT connector flows, and a hosted, authenticated deployment. See SECURITY.md and CHANGELOG.md.

About

MCP server that checks whether your idea already exists on GitHub before you build it. Ranking weights are configurable.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages