{
 "version": "2026-10-25.v7",
 "formula": "score = sum over inputs of weight_i * x_i, where each x_i is in [0,1]. Max 100. Results are sorted by this final score (ties by relevance). Filters (constraints) remove results; they never change scores.",
 "pledge": "No pay-to-rank. No input is purchasable. Sponsorship, if it ever exists, will never touch scores or order.",
 "inputs": [
  {
   "key": "relevance",
   "weight": 45,
   "x": "0.55 x lexical + 0.45 x semantic. Lexical: rarity-weighted share of the job's meaningful terms found in the entry, by field: name/title/publisher 1.0, capabilities/tool names 0.9, summary only 0.75 (0.5 for one-word queries); a synonym hit counts 0.8 (word-form variants such as registry/registers count 1.0). In queries of 3+ terms, up to one term in three may be treated as incidental when most terms match: it is dropped from the denominator at 0.85x per dropped term. Each term weighs ln(1 + N/(df+1)); generic words (api, mcp, server, tool, data, search, ...) weigh 0.2x; filler words (free, best, how, ...) are ignored. Semantic: cosine similarity between the job and the entry's description/capabilities embedding (Workers AI bge-small-en-v1.5, Vectorize), mapped from [0.50, 0.70] to [0,1]. Entries without an embedding get lexical x 0.85. Entries with no capability or tool metadata count summary hits at 0.85 (their summary is their only descriptor). Geographic scope: when the job names no place, entries limited to one named country/region get x0.92 and entries stating multi-country coverage (global, worldwide, N countries/registers) get x1.04; when the job names a place, entries naming it get x1.04, entries scoped to a different place get x0.7, and the place term is never treated as incidental",
   "thin_when": "never"
  },
  {
   "key": "brand_match",
   "weight": 10,
   "x": "1 when the job names the publisher's own brand (e.g. 'stripe' for stripe.com; a publisher label ending in 'apis'/'api' also matches its stem, e.g. 'google' for googleapis.com) and the entry is vendor-official; 0.5 when it names the publisher but vendor status is unknown; 0 otherwise. Product aliases curated from vendor docs (e.g. 'Gmail', 'Google Calendar') count like the publisher's brand. Case is evidence: in a job that uses capitals, a brand word written only in lowercase (e.g. 'threads' in 'read Gmail threads') is read as a common noun and does not match; in an all-lowercase job, brand labels that are everyday words (threads, calendar, drive, square, linear, ...) do not match on their own. Shared-hosting subdomains (*.vercel.app etc.) never brand-match",
   "thin_when": "never"
  },
  {
   "key": "official",
   "weight": 6,
   "x": "1 only when vendor-official: a first-party listing, a curated vendor entry, or a registry server whose verified publisher domain matches the server's own name. Publisher domain verification alone (publisher_verified) does not count: a verified third party wrapping another vendor's API is not official",
   "thin_when": "unknown"
  },
  {
   "key": "handshake",
   "weight": 8,
   "x": "last MCP handshake by our checker: 1 = ok within 7 days; 0.7 = reachable but requires auth; 0.5 = ok within 30 days; 0 = failed or never checked; 0.5 (neutral, 'not applicable') for vendor-official entries with no MCP endpoint, which cannot be handshake-checked. Bundles: only one server is probed (stated in evidence)",
   "thin_when": "never checked"
  },
  {
   "key": "uptime",
   "weight": 5,
   "x": "share of our checks in the last 30 days that reached the server when there are 3+ checks; 0.5 (neutral, 'insufficient data') with 1-2 checks; 0 with none, except 0.5 ('not applicable') for vendor-official entries with no MCP endpoint",
   "thin_when": "fewer than 3 checks"
  },
  {
   "key": "outcomes",
   "weight": 8,
   "x": "0 until at least 2 independent reporters; then (weighted successes + 1) / (weighted reports + 2) x min(1, reporters/4). Report weight: anonymous 0.25, contributor 0.5, signed identity 0.5 + 0.5 x reputation; flagged, operator-disclosed and search-quality reports weigh 0",
   "thin_when": "fewer than 2 independent reporters"
  },
  {
   "key": "auth_friction",
   "weight": 5,
   "x": "none = 1; api_key or oauth = 0.5; partner approval = 0; unknown = 0.3",
   "thin_when": "unknown"
  },
  {
   "key": "effects_declared",
   "weight": 4,
   "x": "1 when per-tool side effects are known (declared, probed or inferred from tool names), else 0. read_only is only claimed when every server in a bundle was verified",
   "thin_when": "unknown"
  },
  {
   "key": "freshness",
   "weight": 5,
   "x": "newer of the source's own update date and the date Toolwork last verified the card against vendor docs: within 90 days = 1; within 365 days = 0.5; older = 0",
   "thin_when": "no date"
  },
  {
   "key": "pricing_known",
   "weight": 4,
   "x": "1 when pricing is known with a source, else 0",
   "thin_when": "unknown"
  }
 ],
 "verdicts": {
  "good_match": "best relevance >= 0.62",
  "weak_matches": "best relevance >= 0.45 and < 0.62",
  "nothing_good_found": "best relevance < 0.45 or no results; we say so rather than pad"
 },
 "best_pick": "the highest-ranked result (by final score) whose relevance >= 0.62; its rank is returned, and if it is not #1 the response says why",
 "evidence_thin": "flagged when the result has never been handshake-checked and has fewer than 2 independent outcome reporters",
 "agent_context": "agent_type and stage are logged and used to attach playbook hints; they do not change scores",
 "reputation": "reputation = (matched + 1) / (compared + 2), where a report is compared when a machine handshake of the same tool exists within 48 hours of it, and matched when they agree",
 "anti_gaming": [
  "30 outcome reports per reporter per day",
  "more than 5 reports for one tool by one reporter in 24h are flagged (weight 0)",
  "reports that contradict a machine check within 48h are flagged contradicts_machine_check (weight 0, still stored)",
  "a single reporter can never move a score: outcomes count only from 2+ independent reporters and are damped until 4",
  "reports from weak or nothing_good_found searches must set used_tool: true or be filed as kind=search_quality (never counted against a tool)",
  "agent reports never count as machine checks; they feed only the outcomes input"
 ],
 "id_scheme": {
  "summary": "IDs are permanent strings '<kind>:<key>'. Use resolve_tool (MCP) or GET https://api.toolwork.org/v1/resolve?url=|repo=|package=|name= to turn a URL, repo, package or registry name into an ID.",
  "patterns": [
   {
    "pattern": "mcp:<registry name>",
    "example": "mcp:com.stripe/mcp",
    "source": "Official MCP Registry server name (reverse-DNS namespace / server)"
   },
   {
    "pattern": "mcp:docker/<catalog key>",
    "example": "mcp:docker/globalping",
    "source": "Docker MCP Catalog key (merged into the registry ID when both list the same repo)"
   },
   {
    "pattern": "api:<apis.guru key>",
    "example": "api:stripe.com, api:twilio.com:api",
    "source": "APIs.guru provider[:service] key"
   },
   {
    "pattern": "api:<vendor domain>[:<product>]",
    "example": "api:cloudflare.com:workers",
    "source": "Toolwork curated entry from the vendor's own docs"
   },
   {
    "pattern": "cli:<vendor domain>:<tool> | lib:<package> | app:<vendor domain>",
    "example": "cli:cloudflare.com:wrangler, lib:puppeteer, app:cursor.com",
    "source": "Toolwork curated non-HTTP tools"
   },
   {
    "pattern": "mcp:<vendor domain>",
    "example": "mcp:vercel.com",
    "source": "Toolwork curated vendor-official MCP server not (yet) in the Official MCP Registry"
   },
   {
    "pattern": "recipe:toolwork.org:<slug>",
    "example": "recipe:toolwork.org:nextjs-prisma-sqlite-vps",
    "source": "Toolwork-authored recipe that combines existing cards into steps (type 'recipe'; not a product; every step cites vendor docs)"
   },
   {
    "pattern": "stub:<host>[/<path>]",
    "example": "stub:example.com/my-tool",
    "source": "proposed inline by a contributor via share_experience; unverified, pending moderation, never ranked"
   }
  ],
  "stability": "IDs never change once issued; merged entries leave a 'merged' change event pointing to the surviving ID."
 }
}