Skip to content

Report an unlisted registry server as unknown, and skip too-short searches - #176

Merged
M3gA-Mind merged 1 commit into
tinyhumansai:mainfrom
M3gA-Mind:fix/167-registry-404-422
Nov 11, 2026
Merged

M3gA-Mind merged 1 commit into
tinyhumansai:mainfrom
M3gA-Mind:fix/167-registry-404-422

Conversation

@M3gA-Mind

Copy link
Copy Markdown
Contributor

Summary

Two user-input cases against the official MCP registry came back as raw upstream errors and reached Sentry at error level (TAURI-RUST-116W, 601 events and 39 users; 165B, 24 events):

  • A detail lookup for a name the registry doesn't list gets a 404 from /v0/servers/{name}/versions. That surfaced as Error::Http, not the Error::UnknownServer the module already returns for an empty versions document. It's now mapped to UnknownServer, so the curated fallback and a host's unknown-server handling apply.
  • A search of one or two characters was sent as search=, and the registry refuses it with 422 ("at least 3 characters after trimming"). A query that short is no longer sent as a search, and the page comes back unfiltered, as for an empty query.

Related issue

Closes #167. The host side, treating UnknownServer as expected user state with a friendly message, belongs in OpenHuman (mcp/registry/ops.rs).

API or behavior changes

  • McpOfficialRegistry::get returns UnknownServer for a registry 404, including through the local-index lookup, where it used to surface as Http { status: 404 }.
  • A query under 3 characters, after trimming, gets the unfiltered page.

Validation

On our Linux CI runner, at e4b8cf8:

  • cargo fmt --all -- --check: clean.
  • cargo clippy --all-targets --all-features -- -D warnings: 0 errors, same as main.
  • cargo build --all-targets --all-features: ok.
  • cargo test -p tinymcp registry::sources::official: 150 passed, 0 failed.
  • cargo test --workspace: no failures unique to this branch. The 5 failures are main's known full-suite timeouts in registry::boot and tinybus_module::directories.
  • Red controls, each restored afterwards:
    • treating the 404 as an upstream error again: both a_name_the_registry_does_not_list_is_an_unknown_server and the index lookup test fail;
    • sending a too-short search again: the search test fails.

Tests

  • a_name_the_registry_does_not_list_is_an_unknown_server uses a stub registry answering 404.
  • a_query_shorter_than_the_registry_minimum_is_not_sent_as_a_search checks that "a" and " ab " send no search while "abc" does.
  • an_uncurated_server_the_registry_does_not_list_is_still_an_error (index) used to expect the raw Http { status: 404 }. It now expects UnknownServer, which is the change this PR makes; it is still an error, and a curated server is still described from its entry.
  • Each test fails when its line is reverted to main's code.

Documentation

The rustdoc on get and on MIN_SEARCH_CHARS.

Checklist

  • The change is focused on one logical change
  • No new #[allow(...)], #[ignore], or relaxed lints
  • No secrets, tokens, or .env contents in the diff or the description

…rches

Two user-input cases against the official MCP registry came back as raw
upstream errors (625 Sentry events, 39 users):
- a detail lookup for a name the registry does not list gets 404, which
  surfaced as Error::Http rather than the Error::UnknownServer the module
  already returns for an empty versions document;
- a search of one or two characters was sent as search=, and the registry
  refuses it with 422 (at least 3 characters after trimming).

get_listed now maps a 404 from the versions endpoint to UnknownServer, so
the curated fallback and a host's unknown-server handling apply to it. A
query shorter than 3 characters after trimming is no longer sent as a
search, and the page comes back unfiltered, as for an empty query.

The host side (treating UnknownServer as expected user state instead of
an unexpected failure) is openhuman's part of tinyhumansai#167.

Closes tinyhumansai#167
@coderabbitai

coderabbitai Bot commented Nov 11, 2026

Copy link
Copy Markdown

Important

  • Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 8acb3e7f-7936-4a61-a591-d1b3323ec90a
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@tinysweeper

tinysweeper Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Tiny Sweeper review

Reviewed head: e4b8cf815d2f996328a67818000b9e31dc1dc4cc

Verdict: Ready for maintainer review · Priority: none
Coverage: Reviewed 3 of 3 files.
Pull request purpose (title): Report an unlisted r…

Diagnostics and supporting details

Full summary

Pull request purpose (title): Report an unlisted registry server as unknown, and skip too-short searches. This pull request turns two user-input cases against the official MCP registry into ordinary, expected outcomes instead of raw upstream errors: a detail lookup for a name the registry does not list (a 404 from `/v0/servers/{name}/versions`) now returns `Error::UnknownServer` rather than `Error::Http`, and a search query shorter than the registry's three-character minimum is no longer sent as a `search` parameter, so the unfiltered page is served instead. Both changes are documented in rustdoc and exercised by new and updated tests around the changed code. The latest-push delta is unavailable; there is no base-to-head comparison, so nothing below is a push delta. No findings were reported by any answered lane.

Review snapshot

Change surface Files Review signal Count
Production 1 Active findings 0
Tests 2 Noted findings 0
Documentation 0 Resolved findings 0
Configuration 0 Pending checks/questions 0

Completeness: Complete
Test assessment: Test coverage is assessed from changed tests and lane evidence; execution is not claimed without trusted check data.

What changed

Behaviorally, `McpOfficialRegistry::get` now matches the detail response against `Error::Http { status: 404, .. }` and converts it into `Error::UnknownServer { server }` carrying the qualified name, so an unlisted server is reported the same way as an empty versions document and the curated fallback and host unknown-server handling apply, as described in the `# Errors` rustdoc on `get` (crates/tinymcp/src/registry/sources/official/mod.rs, crates/tinymcp/src/registry/sources/official/mod.rs#impl McpOfficialRegistry {). The local-index lookup test was updated to expect `UnknownServer` for an uncurated name that the registry does not list, where it previously expected `Http { status: 404 }`, while a curated server is still described from its entry (crates/tinymcp/src/registry/sources/official/index_tests.rs#async fn an_uncurated_server_the_registry_does_not_list_is_still_an_error() {). For search, a new `MIN_SEARCH_CHARS` constant documents the registry's three-character (post-trim) floor, and `fetch_page` only attaches the `search` parameter when the trimmed query meets it, otherwise fetching the unfiltered page as for an empty query (crates/tinymcp/src/registry/sources/official/mod.rs). The test harness gained a `searches` field so each request's `search` parameter is recorded for assertions (crates/tinymcp/src/registry/sources/official/mod_tests.rs#struct Seen {, crates/tinymcp/src/registry/sources/official/mod_tests.rs#async fn paged_registry(pages: usize) -> (String, Arc<Seen>) {).

Features

  • Modified — Registry 404 on a detail lookup reports `UnknownServer`: A name the official registry does not list is no longer surfaced as a raw `Http { status: 404 }` error; it becomes the expected `UnknownServer` state, matching the existing empty-versions answer so the curated fallback and host-side unknown-server handling apply. (crates/tinymcp/src/registry/sources/official/mod.rs impl McpOfficialRegistry {, crates/tinymcp/src/registry/sources/official/mod.rs)
  • Modified — Searches shorter than the registry minimum are not sent as a search: A query under three characters after trimming is no longer sent as `search` and triggering the registry's 422; the caller gets the unfiltered page, the same as for an empty query. (crates/tinymcp/src/registry/sources/official/mod.rs impl McpOfficialRegistry {, crates/tinymcp/src/registry/sources/official/mod.rs)
  • Added — `MIN_SEARCH_CHARS` constant documents the registry's search floor: The three-character post-trim minimum is named and documented, with the registry's 422 message recorded next to it, so the guard's origin is explicit at the call site. (crates/tinymcp/src/registry/sources/official/mod.rs)
  • Modified — Local-index test expects `UnknownServer` for an uncurated, unlisted name: The index-path expectation moved from the raw 404 to `UnknownServer`, pinning the new mapping through the local-index lookup while keeping the case an error and keeping curated entries described from their entry. (crates/tinymcp/src/registry/sources/official/index_tests.rs async fn an_uncurated_server_the_registry_does_not_list_is_still_an_error() {)
  • Internal refactor — Test registry harness records the `search` query parameter: Each paged request's `search` value is captured alongside cursors and versions, enabling assertions about whether a query was sent as a search. (crates/tinymcp/src/registry/sources/official/mod_tests.rs struct Seen {, crates/tinymcp/src/registry/sources/official/mod_tests.rs async fn paged_registry(pages: usize) -> (String, Arc<Seen>) {)

Tests

  • new — A stub registry answering 404 to the detail endpoint is queried for `@acme/nothing`, and the returned error must be `Error::UnknownServer` naming that server rather than an HTTP error.: Pins the 404-to-`UnknownServer` mapping at the adapter boundary; the tests lane reviewed it as one of the two behavioural changes carrying a test that would fail if the mapping regressed, and reported no defect. No execution result is asserted here. (crates/tinymcp/src/registry/sources/official/mod_tests.rs, crates/tinymcp/src/registry/sources/official/mod_tests.rs async fn paged_registry(pages: usize) -> (String, Arc<Seen>) {)
  • new — Runs searches for `"a"`, `" ab "`, and `"abc"` and asserts the recorded `search` parameters are `None`, `None`, and `Some("abc")`, exercising the trimmed short-query boundary on both sides.: Holds the three-character floor at the boundary, so removing or widening the guard would change the recorded query parameters; the tests lane treated it as covering the second behavioural change and reported no defect. No execution result is asserted here. (crates/tinymcp/src/registry/sources/official/mod_tests.rs, crates/tinymcp/src/registry/sources/official/mod_tests.rs struct Seen {)

Findings

No active actionable findings.

Before merge

None.

How this fits together

flowchart LR
  n0["McpOfficialRegistry<br/>changed"]:::changed
  n1["Seen<br/>changed"]:::changed
  n2["...ng_a_slash_is_sent_as_one_escaped_segment<br/>changed"]:::changed
  n3["paged_registry<br/>changed"]:::changed
  n4["the_first_page_is_fetched_without_a_cursor<br/>changed"]:::changed
  n5["auth_at"]:::impacted
  n6["store"]:::impacted
  n7["cursors"]:::impacted
  n8["adapter"]:::impacted
  n9["...search_skips_the_network_for_the_cooldown"]:::impacted
  n2 -->|calls| n3
  n2 -->|tests| n3
  n2 -->|calls| n5
  n2 -->|tests| n5
  n2 -->|calls| n6
  n2 -->|tests| n6
  n2 -->|calls| n8
  n2 -->|tests| n8
  n3 -->|uses| n1
  n4 -->|calls| n3
  n4 -->|tests| n3
  n4 -->|calls| n5
  n4 -->|tests| n5
  n4 -->|calls| n6
  n4 -->|tests| n6
  n4 -->|calls| n7
  n4 -->|tests| n7
  n4 -->|calls| n8
  n4 -->|tests| n8
  n8 -->|uses| n0
  n9 -->|calls| n5
  n9 -->|tests| n5
  n9 -->|calls| n6
  n9 -->|tests| n6
  classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
  classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
  classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
  classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Loading
Agent review details

critique

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: Reviewed all 3 changed files with 0 findings.
  • Lane summary: Reviewed 3 files; 0 findings.

security

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: Reviewed all 3 changed files with 0 findings.
  • Lane summary: Reviewed 3 files; 0 findings.

tests

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: The lane reports both behavioural changes carry tests that would fail on regression: the 404-to-`UnknownServer` mapping through the stub registry and the updated index case, and the 3-character search floor at its boundary; no reportable defect found.
  • Lane summary: Both behavioural changes carry tests that would fail if the behaviour regressed: the 404-to-`UnknownServer` mapping is pinned in `a_name_the_registry_does_not_list_is_an_unknown_server` (and the updated `index_tests.rs` case for an uncurated name), and the 3-character search floor is exercised at the boundary in `a_query_shorter_than_the_registry_minimum_is_not_sent_as_a_search`, which would see the query params change if the branch were removed or widened. No reportable defect found.

commits

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: No sensitive content reported in the committed files.
  • Lane summary: No sensitive content in committed files.

description

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Positive: The description matches the diff: the 404 mapping to `Error::UnknownServer` in `get` with its `# Errors` rustdoc update, and the `MIN_SEARCH_CHARS` guard in `fetch_page`, are exactly what the body claims, and the new and updated tests correspond to the described validation; nothing problematic surfaced.
  • Lane summary: The description matches the diff: the 404 mapping to `Error::UnknownServer` in `get_listed` (with its `# Errors` rustdoc update) and the `MIN_SEARCH_CHARS` guard in `fetch_page` are both exactly what the body claims, and the new and updated tests correspond to the described validation. The stated API/behaviour changes are accurate and no significant change in the diff goes unmentioned; nothing problematic surfaced.
Evidence and run details
  • Models: ladder/vectors-oai3, deepseek-v4.1-flash
  • Spend: $0.013351
  • Tokens: 314990 input · 48166 output · 119168 cached · 291 embedding
  • Updated: 2026-11-11T12:57:54Z
Head State Pass summary
e4b8cf815d2f ready for maintainer review 0 active finding(s), 0 resolved finding(s) (at 2026-11-11T12:57:54Z)

tinysweeper 0.1.2

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tinysweeper found nothing blocking. Approving.

Diagnostics

Total spend: $0.0134

@M3gA-Mind
M3gA-Mind merged commit 1caa269 into tinyhumansai:main Nov 11, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Official MCP registry: lookup of an unlisted server (404) and searches under 3 characters (422) surface as raw HTTP errors and reach Sentry

1 participant