Repository navigation
Conversation
|
This pull request was closed because it must link to an issue assigned to you. Once this PR links to an issue and a maintainer assigns that issue to you, it will reopen automatically. Please update this PR rather than opening a new one. Per CONTRIBUTING.md, an external PR must reference an issue that's assigned to its author. To get there:
Once you're assigned and the link is present, this PR reopens automatically — no further action needed. Maintainers: reopen this PR or remove the |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 37f0c6be4d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
3092731 to
bdaafae
Compare
A parent VersionFilter forwards its range as an explicit VersionSpec to a mounted child. The child treated any non-None spec as an exact request and skipped its disabled-version fallback, so listing advertised an older enabled version while versionless get_*/call_tool/read_resource/ render_prompt failed. Only VersionSpec.eq now counts as an explicit exact request; ranges fall back within the range. Fallback candidates are re-resolved highest-first through the normal get_* lookup so lookup-only refusals, session transforms, auth, and enabled state are honored rather than trusting the listing. Resource lookup still reports whether a concrete resource exists so hidden resources keep shadowing matching templates.
bdaafae to
39ac49d
Compare
|
Follow-up on the range fallback in this PR: resolved a regression where PEP 440-equivalent version spellings could be listed but not resolved.
The fallback now groups candidates by normalized version and asks the lookup path once per group:
This also fixes the two same-root-cause cases from the review: an unversioned component being looked up as "highest version" when a higher same-name version is disabled (all component types), and duplicate same-name same-version components from different providers. Tests: Known limitation: a lookup-only refusal cannot be evaluated per component when two components share one normalized version (same name and version from different providers, or equivalent spellings), because the lookup path can only admit or refuse the version as a whole. Version-keyed refusals, the pattern the transform API exposes, are honored for the whole group. |
…er-merge # Conflicts: # fastmcp_slim/fastmcp/server/server.py
|
Merged current main into the branch in eef9f6f, combining the range-fallback logic with main's auth-aware highest-available-version fallback. Targeted runs: tests/server/versioning 145 passed; tests/server/auth/test_authorization.py 102 passed, 2 skipped; tests/server/test_server.py 25 passed. |
There was a problem hiding this comment.
Automated review by the FastMCP triage reviewer bot.
The same-version fallback can execute a tool its provider refuses. With two custom providers listing [email protected], disabling the first provider's tool lets its successful lookup admit the second provider's listed tool even when that provider's get_tool returns None. Component auth checks do not enforce this lookup refusal.
Preserve each candidate's lookup authority instead of admitting all listed siblings after one version-level lookup succeeds. Add a disabled-first/refused-second provider regression that asserts the refused function never executes, while retaining enabled-duplicate coverage.
Checks
Independent provider probe executes the refused function at this head; baseline returns None. Focused tests: 42 passed at head, 6 failed on baseline. Adjacent versioning tests: 103 passed.
|
CI caught one merge regression: a plain resource lookup miss was falling back to list_resources, which reached mounted servers and invoked nested middleware. Fixed in 97d7a69: a plain miss is final again; only denied/disabled resources fall back (exact requests stay final). Re-ran: versioning+middleware 146 passed; middleware nested + auth authorization + server 144 passed, 2 skipped. |
|
Addressed in 3d907e7. Verification: 146 versioning tests, 1583 auth+provider tests, |
Closes #5837
Description
With a parent
VersionFilterover a mounted child and the child's latest component version disabled, the parent'slist_*advertise the older enabled version (v1.0) but versionlessget_tool/get_resource/get_resource_template/get_promptreturn None, socall_tool/read_resource/render_promptfail with Unknown tool / Resource not found / Unknown prompt. Only explicitversion=VersionSpec(eq="1.0")calls work.Root cause:
VersionFilterforwards its policy range to the mounted child as an explicitVersionSpec. The child treats any non-None spec as an exact request, skips its disabled-version fallback, and returns None for the highest in-range version; the parent then early-returns None (its fallback only covers a non-None-but-disabled component).What changed
Server.get_tool,get_resource,get_resource_templateandget_promptnow treat onlyVersionSpec.eqas an explicit exact request. Range specs resolve to the highest enabled version within the range, selected from the filtered listing usingversion.matches(...)andversion_sort_key.super().get_*(..., VersionSpec(eq=candidate.version))path. Listing is not authoritative for resolution, so lookup-only refusal transforms and lookup-time auth checks are honored rather than bypassed. A resolved component must match the candidate's identity and version, passes through session transforms, and must be enabled.eqrequest remain authoritative; lookup-only refusal semantics for unconstrained and exact lookups are preserved.VersionFilteritself; explicitVersionSpec.eqbehavior is unchanged.Contribution type
Bug fix verification
Test paths:
tests/server/versioning/test_mounting.py::TestMountedVersionFilterWithDisabledLatest(five tests) and::TestMountedVersionFilterLookupRefusals(two tests).Red at
adf90b66(fix stashed, tests kept):Red before the review follow-up (at d991088, follow-up stashed): the lookup-only refusal test got a v2 tool instead of None (
assert FastMCPProviderTool(name='calc', version='2.0', ...) is None).Green at the final head: 5 passed + 2 passed.
The regression tests cover all four component types (list vs versionless get vs call/read/render), plus controls: v2 enabled still resolves to v2; no parent filter + v2 disabled falls back to v1; explicit
VersionSpec(eq="1.0")works and exact eq v2 remains unserved; filter on the child directly falls back; a lookup-only refusal transform under the filter still returns None for versionless get while listing passes through; an auth-denied highest version falls back to a public lower version.Validation
uv run --frozen pytest tests/server/versioning/test_mounting.py -q-> 20 passeduv run --frozen pytest tests/server/versioning tests/server/test_hashed_tool_dispatch.py -q-> 161 passeduv run --frozen pytest tests/server/versioning tests/server/transforms tests/server/providers tests/server/test_server.py tests/server/test_hashed_tool_dispatch.py tests/server/test_session_visibility.py tests/server/test_providers.py -q-> 1600 passed, 26 warningsuv run --frozen ruff checkandruff format --checkon the changed files -> cleanuv run --isolated ty check-> All checks passedAI assistance
Tool(s) used: Claude Code
How you used it: investigated from a verified reproduction, drafted the range-aware fallback and regression tests, and revised the fallback in response to review.
Checklist
uv run prek run --all-filesand all checks pass (focused checks passed)Review follow-up
Codex review found a residual P1 path: when a range lookup resolved the highest version and later marks it disabled, the fallback could take a lower candidate straight from the listing, bypassing a lookup-only transform or provider that refuses that lower version. The fallback now re-resolves every listed candidate through the lookup path for both the lookup-miss and resolved-but-disabled cases, for all four component types. Regression tests cover disabled-highest plus lookup-refused-lower for tools, resources, templates and prompts.
The branch was also rebased onto the upstream
get_resource→_lookup_resourcerefactor: the range fallback now lives inside_lookup_resourceand preserves the new(resource, exists)hidden-vs-missing contract, with the same re-resolution rules for tools, resources, templates and prompts.The triage review then found that a same-version duplicate could still be served from the listing after its own provider refused the lookup. Same-version components from different providers are indistinguishable by name and version, so the fallback now asks each provider whether its own lookup admits the candidate; a listed sibling that no provider admits is refused.
TestProviderLookupAuthoritycovers a disabled first provider plus a lookup-refusing second provider, whiletest_duplicate_same_version_resolves_enabled_providerkeeps the enabled-duplicate path covered.