* Fix percent-encoded skill file names unreadable in resources mode
Encode supporting-file paths explicitly (quote/unquote) when building
and resolving skill:// resource URIs, instead of relying on AnyUrl's
implicit encoding. This also closes the ambiguity where a file literally
named "setup%20guide.md" would collide with "setup guide.md" once both
were percent-encoded.
Fixes#4545
* Quote main_file_name when building its resource URI
Keeps the main-file URI on the same explicit quote/unquote round-trip
as supporting files, so a custom main_file_name containing a literal
'%' still resolves after the shared unquote() in _get_resource().
Preserve explicit suffixes when building data-backed File resource URIs, while keeping the inferred-extension fallback for names without a suffix.
Closes#4530
Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
* Add M2M client credentials auth providers
Wrap the SDK's client_credentials and private_key_jwt OAuth providers as
FastMCP-idiomatic ClientCredentialsOAuthProvider and PrivateKeyJWTOAuthProvider,
enabling browser-free client authentication via Client(auth=...).
* Fix M2M token cache collision and explicit-scope drop
Namespace the token cache by client_id so distinct clients sharing one store don't overwrite each other's tokens; pin caller-supplied scopes so the token request keeps them; fix CodeQL URL-substring check in tests; drop unused logger.
* Preserve step-up scope union, scope-aware token cache, restore token expiry
Only pin the caller's explicit scopes on initial authorization, leaving the SDK's step-up scope union intact; namespace the token cache by requested scopes as well as client_id; restore persisted absolute expiry on init so an expired stored token is re-fetched.
* Skip expiry restore for non-expiring reloaded tokens
* Distinguish expires_in=0 from omitted when restoring expiry
* Scope step-up flag to the flow via ContextVar; runnable JWT signing example
* Add server-side argument completion (@mcp.completion)
* Reference CompletionValues directly in cast so the import reads as used
* Import completion types from mcp_types, not the fastmcp.types mirror
* Fix test imports after dropping the fastmcp.types mirror
* Fix change-register example import after dropping the types mirror
* Enforce 100-value completion cap; make docs example runnable
* Document completion authorization contract
* Offload sync completion handlers to threadpool
* Exclude bare str from completion return type
* Pass Any-typed value in bare-string rejection test
* Point completion authoring types to mcp_types in v4 notes
* Trim fastmcp.types to FastMCP-unique types only
fastmcp.types re-exported 29 mcp_types symbols verbatim, which was
pointless indirection users had to discover. It now holds only Textarea,
the one type FastMCP actually defines; everything else imports from
mcp_types directly. These mirrors were added during unreleased SDK v2
migration work and never shipped, so this is not a breaking change.
* Keep historical mcp.types import in v2/v3 migration examples
fastmcp.types re-exported 29 mcp_types symbols verbatim, which was
pointless indirection users had to discover. It now holds only Textarea,
the one type FastMCP actually defines; everything else imports from
mcp_types directly. These mirrors were added during unreleased SDK v2
migration work and never shipped, so this is not a breaking change.
* Restore upgraded dependency checks
Generated with Codex
* Clarify settings loading and teardown logging
Generated with Codex
* Align ty checks on the upgraded version
Generated with Codex
* Preserve simultaneous caller cancellation
Generated with Codex
* Delegate forked client protocol helpers to the SDK
Replace FastMCP's copies of _fold_extensions, _evicting_message_handler,
and _synthesize_discover with imports from mcp.client.client. The fork had
drifted: it was missing validate_extension_identifier, so non-reverse-DNS
extension identifiers were silently accepted.
Full lifecycle composition over mcp.Client stays blocked upstream —
mcp.Client hardcodes ClientSession construction (no session_class hook)
and forbids reentry.
* Drop duplicate local helpers reintroduced by the merge; use SDK versions
Resource/prompt error detail and proxy instructions/connection-error
surfacing now work on the modern protocol era, so the tests pinned to
mode="legacy" with a TODO(defect)/TODO(mode="legacy" pin) marker run
on the default auto mode again.
Three defects hidden by tests pinned to the handshake era, where a raw
exception reaches the wire as str(exc). At 2026-07-28 the runner masks
anything that is not an MCPError/ValidationError as "Internal server error".
- _on_read_resource / _on_get_prompt now translate FastMCPError through
to_mcp_error, mirroring _on_call_tool. Masking is unchanged.
- FastMCPProxy registers a server/discover handler so upstream instructions
reach modern clients; on_initialize only fires for the handshake.
- ProxyProvider's list methods normalize transport failures into MCPError.
Removes pins added while making the auto-default suite pass that weren't
actually testing older-protocol-only behavior, and keeps (with a stated
reason) the ones that are. Along the way, fixes two real defects the audit
surfaced in the modern protocol path: PingMiddleware could leak a
_active_sessions entry when a connection's exit_stack closed before its
keepalive task got its first scheduler turn, and FastMCP(experimental_
capabilities=...) was silently dropped from server/discover responses
(it only ever reached the legacy initialize handshake).
Most pins in tests/server/providers/proxy/ were added only to keep tests
green while unpinning changed which protocol era the proxy's backend
connection used, not because the test's subject cared about the era. With
proxy era-mirroring (#4573) landed, a front client on auto correctly moves
the whole chain to the modern protocol, so plain tool/resource/prompt calls
through a proxy no longer need a pin.
Kept pins fall into three buckets, each commented at the call site: tests
whose subject is genuinely handshake-only (sampling, roots, elicitation
push-forwarding, ping, initialize handshake mechanics); tests whose backend
is a directly-constructed ProxyClient/StatefulProxyClient, which always
defaults to legacy independent of the front era; and two tests left pinned
with a TODO documenting a real defect this audit surfaced (upstream
instructions not forwarded to a modern-era client through a proxy, and
ProxyProvider.list_tools leaking an unwrapped connection error instead of
an MCPError).
Remove 4 unjustified pins (proxy header passthrough, connect timeout,
two response_title validation tests that fail before any request is
dispatched). Keep 81 pins that genuinely exercise older-protocol-only
behavior (ctx.elicit back-channel, sampling, roots, ping, session IDs,
initialize handshake, client.set_logging_level).
Flags a real defect: _on_read_resource/_on_get_prompt only catch
(DisabledError, NotFoundError), unlike _on_call_tool which catches
FastMCPError broadly. A ResourceError/PromptError escapes as a raw
exception and the modern protocol's generic exception ladder masks it
as "Internal server error", losing the detailed message tool errors
still get. Left pinned with a TODO in test_client.py and
test_error_handling.py rather than hidden.
* Mirror front protocol era onto proxy backend connection
A proxy created from a non-Client target now negotiates, on its backend,
whatever era its front client negotiated, instead of pinning one era.
Explicit create_proxy(mode=...) still overrides. Guards the eager backend
initialize() so an explicit modern pin behind a handshake front no longer
crashes.
* Carry the mirrored proxy era into multi-server config backends
A multi-server MCPConfig target mounts one proxy per configured server on a
composite router, so setting the era on the outer client stopped at the router
and every real backend stayed on its default era. TransportOptions.backend_mode
carries it down, resolved per request alongside the outer mirroring.
The router is also sealed under a policy held on the transport rather than a
fresh per-router ephemeral key, so a guard tool's request_state survives the
router being rebuilt between rounds.
* Fix: gather() eagerly creates coroutines before scheduling them
AggregateProvider fans out Provider.get_tool() (and sibling calls) across
child providers via gather(*[p.get_tool(x) for p in providers]). The list
comprehension builds every coroutine up front, then gather()'s scheduling
loop hands them to an anyio task group one at a time. If that loop is
interrupted partway through - e.g. by pytest-timeout's SIGALRM-based
per-test timeout, which can fire between any two bytecode instructions,
unlike normal async cancellation - any coroutine not yet scheduled is
abandoned and silently garbage collected later, producing a "coroutine
'Provider.get_tool' was never awaited" warning attributed to whatever
unrelated test happens to be running when the GC gets to it.
Change gather() to take a single iterable consumed lazily, one awaitable
at a time, right before each is scheduled, and close any awaitable that
was just retrieved if scheduling it raises. Update call sites to pass
generator expressions instead of eagerly-built lists so coroutine
creation and scheduling stay tightly coupled.
* Close unscheduled awaitables from eager callers; make get_tasks lazy
The guard failed on any denial, so an agent falling back to an unlisted tool
during a GitHub outage tripped it — and the error blamed the allowlist, which
was intact. It now fails only when a command the workflow actually grants is
refused, which is the signal that a pattern was mangled.
mcp__github__get_pull_request was never granted, so on a PR the agent could
only read via get_issue and reached for denied fallbacks when that failed.
Client negotiates the newest mutual protocol era by default (probe
server/discover, fall back to the initialize handshake). ProxyClient and the
inspect utility explicitly pin the handshake era so proxy forwarding and
server_info reads are unchanged. SSE and multi-server config transports are
legacy-only. extensions= and result_claims= (SEP-2133) are thin passthroughs
to the SDK session.
* Make the SDK seam the root of FastMCP middleware dispatch (D3)
Notifications, cancellations, and malformed/unroutable messages now reach
on_message/on_request/on_notification at the SDK seam. Component methods keep
their interior dispatch (typed hooks, tool-exception visibility) unchanged; the
seam covers only messages the interior never dispatches, so each hook fires once.
* Document the middleware seam coverage and suspend semantics (D3)
* Align seam docs and ask-visibility test with the result-cycle MRTR model
An InputRequiredResult is the full result of a complete request->response
cycle, not a suspension: component hooks observe an asking round's
InputRequiredToolResult as an ordinary return value.
* Replace 'seam' language with plain dispatch terminology
* Keep the raw middleware __call__ signature; forward middleware message edits
* Cover fires-once across an MRTR continuation round
* Align cherry-picked coverage test with renamed recorder
* Rewrite only the message, never the dispatch destination