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:
- Open GitHub Settings → Developer settings → Personal access tokens → Fine-grained tokens → Generate new token.
- 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.
- Generate it and supply it as
GITHUB_TOKENthrough 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.
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-firstThe 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-httpThe endpoint is http://127.0.0.1:8000/mcp. Connect an MCP client, then stop the
server with Ctrl+C when finished.
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 --helpStop 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 --helpUse 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.
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.
{
"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.
{"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.
{
"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.
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, 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-firstFor 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-firstPowerShell 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.
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.orgAdd 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.
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.
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.
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.
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 buildFor pip-based development and release checks in a fresh virtual environment:
python -m pip install '.[dev]'
search-first --helpAll 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.