# Toolfinder scoring methodology (v0.3)
Every server gets a public 0-100 score. The formula below is the code (`src/score.ts`); nothing else feeds the score.
**Sponsorship never affects score or organic ranking.** `computeScore()` does not read the `sponsored` field. Sponsored
results appear in a separate, labelled `sponsored` list (max one per query, only for servers scoring >= 50).
| Component | Max | How it is computed |
|---|---|---|
| Health | 25 | Last MCP handshake (Streamable HTTP `initialize` -> `notifications/initialized` -> `tools/list`). OK: 25 at <= 250 ms initialize latency, minus 1 point per extra 250 ms, floor 17. Up but needs credentials (HTTP 401/403 or auth error): 18. Responds but handshake fails: 5. Unreachable/timeout (10 s): 0. Local-only (stdio) or not yet checked: 0. |
| Auth ease | 15 | Remote: no auth 15, OAuth (WWW-Authenticate Bearer / resource_metadata, so one-click capable) 12, static header/API key 6, unknown 4. Local package: 9, or 5 if it requires secret env vars. |
| Tool quality | 15 | Auth-gated servers (reachable, but they need credentials) get a neutral 7.5, so OAuth servers aren't punished for being secured. Otherwise only when `tools/list` succeeds: 4 base + 5 x (share of tools with a description >= 20 chars) + 3 x (share with an input schema) + 3 if <= 40 tools (1.5 if <= 80, 0 above: context bloat). |
| Maintenance | 15 | GitHub last push: < 30 d 15, < 90 d 12, < 180 d 8, < 365 d 4, older 1; archived repo 0. No GitHub data: half credit from registry `updatedAt` (7/5/3/1). |
| Adoption | 10 | `10 x log10(stars + 1) / 4`, capped at 10 (10k stars = full marks). Stars belong to a repo and count **once per repo**: when several servers share a repo, only the server whose name matches the repo name gets full credit; every other sibling gets `full / N`, capped at 3. If no sibling matches the repo name, none of them is canonical. An `io.github.<owner>/…` server whose repo is owned by someone else gets 0 (it is borrowing stars). |
| Usage | 10 | Placeholder, currently 0 for everyone. Will come from anonymised first-party Toolfinder call success rates. |
| Security | 10 | +3 all remotes HTTPS (+1 for local-only packages), +3 public source repo listed, +2 OSI-style license detected, +2 OAuth or no-credential auth (or local package). |
Total = rounded sum. Each server page shows the per-component breakdown and notes.
## Data sources
- Official MCP Registry `GET /v0.1/servers?version=latest&updated_since=...` (cursor paging), every 6 h.
- GitHub REST `GET /repos/{owner}/{repo}` (stars, pushed_at, archived, license), refreshed daily, rate-limit aware.
- Our own health probes, hourly in batches, history kept in `health_checks`.
## Known limitations (v0)
- Local/stdio servers cannot be health-checked, so they max out around 55-60.
- Auth-gated servers can't be tool-quality scored until we hold test credentials.
- Legacy HTTP+SSE remotes are probed (GET stream -> `endpoint` event -> POST). Entries labelled `sse` that answer 404/405 to GET are retried as Streamable HTTP.
## Anti-junk and anti-gaming adjustments (v0.3)
A sanity check of the full registry (43,198 servers, 13 Oct 2026) showed the raw leaderboard could be taken over by template entries and mass publishers. Penalties are subtracted after the weighted sum (floor 0). The server page lists them.
| Flag | Rule | Penalty |
|---|---|---|
| `placeholder` | empty or template description ("Description of my MCP server", "[describe…", "A non functional MCP server") | -20 |
| `retired` | description starts with retired, deprecated, moved to, or "use X instead" | -25 |
| `test_or_demo` | server name segment is test, demo, example, sample, hello-world, playground, tmp, foo, or my-mcp | -15 |
| `cloned_description` | the same description used by 5 or more servers in one namespace | -10 |
| `shared_endpoint` | 3 or more registry entries point at the same remote URL | -10 |
| `bulk_publisher` | the namespace has more than 100 servers (the top 4 namespaces hold 6,089 entries, 14% of the registry) | -5 |
Display rules, which never change the score:
- The public leaderboard shows only unflagged servers, at most 2 per publisher namespace.
- Search halves the relevance of flagged servers.
**GitHub signals are off until coverage is complete.** Unauthenticated GitHub allows 60 requests an hour, which can't cover around 20k repos. Scoring only a sample gave those servers an unfair lead, so GitHub fields are cleared and maintenance uses registry dates (half credit) for everyone. Full enrichment will run once a `GITHUB_TOKEN` is set (5,000 requests an hour, about 4–5 hours for the whole registry). Until then the practical ceiling is about 70/100.
## Categories and search (v0.3)
- 19 categories plus `other`. A weighted keyword classifier reads the name, title and description (weight 3) and the tool names and descriptions (weight 1). The top hit is the primary category, and up to 3 categories with at least a third of the top weight are stored.
- Search uses SQLite FTS5 (porter stemming) over name, title, description, tools and categories, with bm25 weights name 6, title 6, description 2, tools 1, categories 2. Final rank = 0.5 × relevance + 0.15 × query-term coverage in name/title + 0.25 if a query term matches the verified publisher namespace (e.g. "stripe" → `com.stripe/*`) + 0.3 × score/100. Flagged results are multiplied by 0.5, and down or erroring ones by 0.85.