Skip to main content
Search ranks published listings against a free-text query. Hard constraints, whether passed as parameters or written in the query (“on testnet”, “under 1 cent”, “mcp tools”), are applied as filters before anything is ranked, and a result never violates one. Ranking fuses a lexical and a semantic ranking. Everything runs in the service process; no query leaves it.
On the hosted service this returns the public demo seller’s weather endpoint first. The response below is illustrative.

Parameters

All filters, including asset and maxPrice, must hold for one and the same payment option of a listing, and a result shows only the payment options that satisfy them. Price and symbol constraints fail closed: an option in an asset the instance does not know never satisfies them. A dollar ceiling applies only to options in a US-dollar stablecoin the instance accepts (USDC, PYUSD, USDT or USDP by symbol), and converts exactly to the asset’s base units.

Response

Constraints in the query

The query is parsed deterministically. A constraint needs an explicit trigger, such as a preposition or a clause of its own. A bare mention inside a sentence stays in the ranked text, because a wrong hard filter silently hides every right answer.
  • Asset and asset-denominated price constraints are recognised only for symbols of assets the instance accepts (TESTNET_ASSETS, PUBNET_ASSETS). A number without a currency (“up to 100 requests per second”) is not a price.
  • Recognised phrases are removed from the text before ranking. A query that is only constraints, such as mcp tools on testnet, returns every matching listing: domain-verified first, then origin-verified, then the longest-listed (method: "filter"). Settlement counts play no part, since a seller could buy them.
  • An explicit parameter overrides the same constraint in the text, and recognised then lists only what was taken from the text and applied.

Ranking

1

Filter

The candidate set is every published listing that satisfies every hard filter.
2

Lexical

BM25F over all candidates, with field weights: name 3 (service name, tool name, path words), tags 2, description 1.5, schema 1 (parameter names and descriptions), host 0.5. The lexical ranking is never truncated.
3

Semantic

The query and each listing are embedded locally with all-MiniLM-L6-v2 (ONNX, CPU, pinned revision and SHA-256 hashes). Candidates with cosine similarity at least SEARCH_SIMILARITY_FLOOR (0.3 by default) are ranked, and the top 100 are kept.
4

Fuse

Reciprocal rank fusion (k = 60) combines the two rankings. Equal scores are ordered domain-verified first, then origin-verified, then by how long the listing has been published, then by id.
5

Group

A resource sold on several networks has one listing per network. It is returned once, at the rank of its best-ranked listing, with every matching network’s options, like /discovery/resources. Pages and cursors count resources.
With SEARCH_EMBEDDINGS=false the service does not load the model and search is lexical-only: ranked responses (method: "lexical") have partialResults: true, while filter-only responses do not. A search whose query cannot be embedded, because the model fails or takes over 2 seconds, is answered the same way rather than failing. Each process builds its first index at startup, and /ready stays failing until it is built. After that it checks every 5 seconds whether the catalog changed and rebuilds the index in the background. A search reads the catalog revision at most once a second; one that arrives after a catalog change, before the background rebuild, builds the new index itself and waits for it. An index is built from the listings and the revision read together, so it never carries a revision without that revision’s changes.

Cursors

Cursors are opaque and HMAC-signed. Each one pins:
  • the request it continues: the normalised query and every filter. Reusing it with a different query or filter is refused;
  • the catalog revision of the first page, so the pages of one search never skip or repeat a result when the catalog changes meanwhile;
  • an expiry, 15 minutes after the page was served. The index a cursor was issued on is kept until its last cursor expires, however often the catalog changes meanwhile.
Replicas that share SEARCH_CURSOR_SECRET accept each other’s cursors while the catalog is still at the cursor’s revision. Without the variable, each process signs with a random key, so cursors fail after a restart or on another replica.

Errors

Quality

Search quality is measured on a frozen, judged dataset of 500 listings and 202 queries, reproducible with pnpm eval, and gated in CI. On the test split, hybrid search reaches nDCG@10 0.742 and Recall@20 0.897 against 0.677 and 0.821 for BM25F alone, with zero filter violations; each filter is also tested on its own with no violation and no missed listing. See Search evaluation.