Skip to content

Benchmark and improve tool-search ranking with indexed BM25 #2996

Open

Description

@sachinkahawala

Describe the feature or problem you’d like to solve

The GitHub MCP Server already exposes tool discovery/search functionality, but
there is no repeatable benchmark for measuring how reliably natural-language
queries retrieve the intended MCP tool.

As the tool inventory grows, a benchmark would make ranking changes measurable
and help prevent retrieval regressions.

This is separate from host-side deferred tool loading discussed in #1680. The
proposal only concerns ranking inside the server's existing tool-search
implementation.

Proposed solution

Add a hand-labelled benchmark covering natural-language intents across the
server's major toolsets, then compare the current heuristic with an indexed
BM25 implementation.

A prototype benchmark contains 49 queries over 115 unique tools and produced:

Strategy Recall@1 Recall@3 MRR@10 Query latency
Current heuristic 71.4% 81.6% 0.792 ~2.25 ms
Indexed BM25 71.4% 87.8% 0.802 ~34 µs
Hybrid RRF 73.5% 87.8% 0.823 ~2.38 ms

Indexed BM25 improved Recall@3 by 6.1 percentage points and was approximately
66x faster per query. The hybrid produced the strongest ranking quality.

Before submitting a PR, I would appreciate maintainer guidance on the preferred
scope:

  1. Benchmark harness only
  2. Benchmark plus indexed BM25
  3. Benchmark plus a hybrid ranking experiment

Example prompts or workflows

  • "Find open issues assigned to me across repositories"
  • "Read the files, reviews, and diff for a pull request"
  • "Download logs for a failed workflow job"
  • "Find exposed secrets detected in a repository"
  • "Add an issue to a GitHub project"

Additional context

The benchmark uses the complete current tool inventory and validates that every
labelled relevant tool exists. The prototype includes unit tests for indexing
tool names, descriptions, parameter names, and parameter descriptions.

Activity

  1. CAOShurong commented on Aug 23, 2026

    @CAOShurong

    I independently prototyped the benchmark-only scope against current main (64a49f3) and think option 1 is the safest first step. It creates a measurable review surface without changing production ranking before the relevance labels themselves have been reviewed.

    The prototype uses 31 static, hand-labelled natural-language cases across 19 toolsets. It builds the real inventory through NewInventory(...).WithToolsets([]string{"all"}).Build(), then fails if a labelled tool is missing or belongs to a different toolset. On the current heuristic it measured:

    Metric Result
    Recall@1 0.903
    Recall@3 0.968
    MRR@10 0.942

    Two implementation details seem worth preserving in an upstream harness:

    1. Validate labels against the current inventory before calculating metrics. That turns tool removal/renaming into an explicit benchmark-data failure instead of silently changing the denominator.
    2. Assert labelled target ranks and aggregate metrics across repeated inventory builds, not the complete ordering of irrelevant results. SearchTools currently has no explicit tie-breaker for equal scores, so requiring full result-list equality would encode an ordering that production does not promise.

    Local verification with Go 1.25.12 passed:

    go test ./pkg/tooldiscovery
     go vet ./pkg/tooldiscovery
     go test ./pkg/inventory
    

    A separate Go benchmark over the same 31 queries measured about 67.9 ms per full run, 26.7 MB, and 53,765 allocations on my Windows amd64 machine. I would keep latency/allocation reporting informational at first because those numbers are hardware-sensitive, while label validity, determinism, Recall@k, and MRR are appropriate regression checks after maintainers review the dataset.

    This dataset is smaller and independently labelled, so its absolute scores are not directly comparable to the 49-query prototype in the issue. The replication does support the proposed sequencing: land an auditable benchmark harness first, then evaluate BM25/hybrid changes in a separate patch against the accepted labels.

  2. CAOShurong commented on Aug 23, 2026

    @CAOShurong

    Follow-up with a separate benchmark-only replication. This is intentionally not treated as the same dataset as the issue's 49-query/115-tool prototype, so the absolute numbers below are not directly comparable.

    Against the current main snapshot 64a49f3, I ran a second 49-case natural-language corpus over the complete registered inventory. The harness validates every labelled tool against the production constructor and checks toolset membership before scoring; repeated inventory builds also produced the same corpus keys and tool-name sequence.

    Observed current-heuristic results:

    Metric Result
    Cases / deduplicated tools 49 / 117
    Recall@1 0.367
    Recall@3 0.571
    MRR@10 0.478

    Local verification on Windows amd64 (Intel Core Ultra 5 125H, Go 1.25.12) passed:

    go test ./pkg/tooldiscovery -run 'TestToolSearchBenchmark' -count=1 -v
    go vet ./pkg/tooldiscovery
    

    The benchmark run measured about 8.25–8.64 ms/query, 1.13 MB/op, and 2,739–2,740 allocs/op. I would keep those performance values informational because they are machine- and corpus-sensitive.

    The result reinforces the sequencing in the issue, but does not by itself justify changing production ranking: first agree on a canonical relevance-labelled corpus and inventory/snapshot semantics; then compare the current heuristic, BM25, and hybrid approaches against that corpus; only then consider a production change. I have not opened a PR or changed the ranking implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions