* Separate proxy protocol policy from client construction
🤖 Generated with OpenAI Codex
* Strip connection-owned request metadata at the proxy backend boundary
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Sanitize forwarded request metadata where the proxy copies it
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Forward hop-safe request metadata for proxied resources, templates, and prompts
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Always emit a tool title, derived from name when unset
Some MCP clients (e.g. ChatGPT) drop tools with no `title` instead of
falling back to `name` for display as the spec allows. Deriving a
default title in Tool.to_mcp_tool() fixes this for every tool built on
top of it, including the search-transform, code-mode, and session
proxy tools that never set one explicitly.
Fixes#4414
* Derive fallback title from the overridden name, document it
Addresses Codex review on #4694.
* Resolve title precedence from effective overrides
* Normalize mapping annotations before deriving the title
* Flatten OpenAPI discriminator subtypes into request bodies
* Resolve schema-name discriminator mappings and union conflicting variant fields
* Advertise discriminator values for propertyless variants and document the behavior
* Remove 3.x-era compatibility shims
* Require response_type in ctx.elicit()
* Name the utilities path for the two non-re-exported auth helpers
* Point sampling handler migration at its submodule
* Add require_roles auth check
* Make role docs runnable standalone and fully annotated
* Treat a scalar role claim as one role; correct step-up docs
* Add v4 version badge to require_roles docs
* Use issuer_url for OAuth issuer identity, not base_url
* Apply ruff format to issuer identity tests
* Align ID-JAG audience docstring with issuer_url
* Make InMemoryOAuthProvider keyword-only like its parent
* Keep ID-JAG audience on base_url, out of scope for issuer identity
* Remove stray scratch script
* Make AuthorizationHandler keyword-only
* Bind ID-JAG audience to the issuer identifier
* Fix double slash in issuer_url well-known log hint
A proxy has no back-channel to the real user, so driving a backend ask inside
it failed outright. Surface it as a result for the parent, as ProxyTool does.
Partial fulfillment means two in-flight updates can carry different answers,
so acknowledging the one that loses the update lock stranded the task on a key
the client had already sent.
Prompt and resource asks carry no content, so caching one stored an empty
result and the client never saw the question. Bypass the cache on
continuation legs and return asks unwrapped, as tool calls already did.
Keep the final outstanding input marker until the next task leg is durable,
so a racing tasks/get cannot read a parked leg as complete. Let resources and
resource templates return InputRequiredResult like tools and prompts. Identify
parked requests by their question rather than sort order.
Deletes fastmcp/server/sampling/, Context.sample/sample_step/list_roots, and
FastMCP(sampling_handler=). The proxy's handshake-era relay now reaches the
front session through the SDK directly.
* Honor OAuth application_type in DCR (SEP-837)
* Simplify web redirect check per ruff SIM103
* Enforce application_type over HTTP, at auth time, and tighten native scheme rules
Recover the DCR application_type the SDK RegistrationHandler drops (P1), enforce the stored type on the authorization redirect path (P2), restrict native to loopback http + custom schemes (P2), and document the web/native rules (P2).
* Fix loopback range detection and use a positive scheme allowlist
Classify loopback hosts with ipaddress.is_loopback so all of 127.0.0.0/8 counts (a web client could bypass the non-loopback rule with 127.0.0.2). Replace the NON_REDIRECT_NETWORK_SCHEMES denylist with STANDARD_URI_SCHEMES: native now accepts only https, loopback http, and unregistered private-use schemes per RFC 8252, so smb/smtp/nfs and other unlisted standard schemes no longer pass.
* Vendor the IANA scheme registry and consolidate the loopback classifier
Replace the hand-picked STANDARD_URI_SCHEMES with a vendored snapshot of the IANA URI scheme registry (423 schemes), so registered transports nobody enumerated (coap, coaps, stun, turn, mqtt) fail closed instead of passing as private-use. Delete the stale duplicate _is_loopback_host in oauth_proxy/models.py and reuse the ipaddress-based classifier from redirect_validation, restoring loopback port flexibility across all of 127.0.0.0/8.
* Treat the reserved localhost namespace and absolute host forms as loopback
RFC 6761 6.3 reserves the whole localhost namespace for the local machine, so app.localhost and localhost. are loopback just as much as localhost. Previously a web client could register https://app.localhost/callback and bypass the non-loopback rule, while native clients were wrongly refused legitimate http://app.localhost:3000 dev callbacks. The suffix test is anchored on a leading dot so localhost.evil.com and notlocalhost stay non-loopback.
* Narrow scope: drop native scheme classification, keep the web rule
Registry membership cannot separate app-dispatch schemes from network transports (vscode is registered because it is an app scheme), so classifying a native client's scheme rejected callbacks that real MCP clients need. Remove the vendored registry and the private-use test; native now accepts any scheme outside the unsafe set, with cleartext http still limited to loopback. Also reject web registrations that omit redirect_uris rather than storing an unusable localhost placeholder.
* Signal component-level scope shortfalls as insufficient_scope (SEP-2350)
* Fix ty type narrowing in scope step-up test
* Respect check short-circuit when reporting scope shortfall (P2)
* Report union of unmet scopes and document step-up contract
* Aggregate scope shortfall across the AuthMiddleware chain
* Stop chain scope aggregation at the first unevaluated gate
* Design doc: stateless session state
* Add stateless session-state primitives: Scope, SessionCodec, scoped state
* Add SessionProvider and Session() annotation for stateless session state
* Rewrite session-state design to final shape (Session object, two patterns, no seal)
* Rework stateless session state to final Session/SessionId design
Remove Scope, SessionCodec/sealing, and scoped ctx.get_state. Add the
Session object (get/set/delete/clear over one dict per (principal,
session_id) key), injected session: Session (keyed by principal, requires
auth), session_id: SessionId argument with auto-filled description, and
SessionProvider contributing create_session/end_session.
* Rename injected marker to UserSession; auto-wire SessionProvider on SessionId
* Document stateless session state as a v4 feature
* Require SessionProvider and make sessions create-then-validate
Remove the implicit SessionProvider auto-wiring; a SessionProvider must now be
registered explicitly. create_session records an owned session and get_session
validates the id, rejecting uncreated or foreign ids.
* Add Session.id (public id for session_id sessions, None for UserSession)
* Fix ty: narrow Tool | None and ToolResult.structured_content in session tests
* Fix session-provider enforcement gap for non-local tools; stop embedding raw principal in UserSession key
* Fix disabled session_id tools blocking listing; reject local tools shadowing SessionProvider lifecycle names
* Decouple SessionId description from lifecycle tool name so it survives namespaced mounts
* Remove SessionProvider enforcement; get_session validation is the guarantee
* Fix stale enforcement/key-format docs; document store-owned session TTL
* Dedup SessionId contract description; tighten context.mdx session-state lead
* Make session store/description resolution work in Docket task workers and for partial tools
* Expose get_session as a standalone task-safe function; drop foreground-only Context.get_session
* Move get_session to dependencies alongside the other request accessors
* Reframe context state docs as Request State; cross-request persistence points to Session State
* Address UserSession injection edge cases from review
- inject a UserSession instance (not bare Session) so isinstance holds
- support session: UserSession | None = None (inject None when unauth)
- detect SessionId params past a partial's positional binding