* 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
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).
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.
* docs: align server component docs
Generated with Codex.
* docs: clarify resource return shapes
Generated with Codex.
* docs: clarify initialize middleware response
Generated with Codex.
* docs: lead visibility filtering with names, scope keys to version targeting
* docs: correct initialize result semantics, template mime type, docket scope, visibility tip
* Warn when a visibility key omits the @ version delimiter
* Honor a resource template's declared mime_type and meta on read
* Strip internal visibility meta from resource content; document filter intersection
---------
Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
* pin pydantic-monty to 0.0.8
* rename tool/prompt/resource base modules to avoid decorator name shadow
* add sys.modules shims for old submodule import paths
* preserve original module paths in deprecation warnings
* clarify when sys.modules shims can be removed
* fix: enforce auth/visibility in ResourcesAsTools and PromptsAsTools for non-FastMCP providers
🤖 Co-authored-by: Claude <noreply@anthropic.com>
* fix: honor stdio auth bypass and correct transform ordering in provider wrappers
Co-authored-by: Claude <noreply@anthropic.com>
* fix: move context/dependencies imports into function to break circular import
* fix: route ResourcesAsTools/PromptsAsTools through ctx.fastmcp
Instead of manually reimplementing auth, visibility, and session
transforms in the transform layer, tool functions now call
ctx.fastmcp.read_resource() / ctx.fastmcp.render_prompt() which
routes through the server's full middleware chain. This matches
the pattern CodeMode uses with ctx.fastmcp.call_tool().
The isinstance(provider, FastMCP) branching is removed entirely.
* feat: add _scope parameter for provider-scoped listing
AggregateProvider can now filter which child providers to query when
listing components. ResourcesAsTools and PromptsAsTools use this to
scope listings to their configured provider while still routing
through ctx.fastmcp for full middleware coverage.
The scope matching walks wrapped providers, so a
WrappedProvider(Namespace, inner=MyProvider) matches if MyProvider
is in the scope list.
* test: add coverage for ResourcesAsTools scoped to a sub-server
* fix: delegate to super() when _scope is None, add AggregateProvider to scope matching
* simplify: remove _scope machinery, route everything through ctx.fastmcp
Reverts the _scope parameter from Provider/AggregateProvider/Server.
ResourcesAsTools and PromptsAsTools now simply route through
ctx.fastmcp for all operations. Apply to a FastMCP server instance
for proper auth/visibility/middleware coverage.
Tests rewritten to use FastMCP server directly instead of raw providers.
* warn when ResourcesAsTools/PromptsAsTools is applied to a non-FastMCP provider
* docs: explain that ResourcesAsTools/PromptsAsTools should wrap a FastMCP server
* raise TypeError instead of warning when applied to non-FastMCP provider
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: Add search transforms for tool discovery
RegexSearchTransform and BM25SearchTransform collapse large tool
catalogs into a search interface so LLMs discover tools on demand
instead of receiving the full listing.
* chore: Update SDK documentation
* fix: call_tool recursion guard, atomic BM25 rebuild, hash includes descriptions
* Extract CatalogTransform base class for catalog-aware transforms
Transforms that replace list_tools() with synthetic components (like
search) need to read the real catalog at call time without triggering
their own replacement logic. CatalogTransform handles the re-entrant
bypass via per-instance ContextVar, exposing transform_tools() as the
subclass hook and get_tool_catalog() for catalog access.
* Add search transform examples for regex and BM25
* Add README for search transform examples
* Polish search example clients with rich output
* Remove hardcoded tool counts from search example subtitles
* Clarify that review bot feedback should be evaluated on its merits
* Expand search transform docs with proper hierarchy
---------
Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>
Change version parameter from str to VersionSpec in enable()/disable()
to support range-based version filtering.
- Add match_none parameter to VersionSpec.matches() for controlling
whether unversioned components match (defaults to True for backward
compatibility, False for enable/disable filtering)
- Update Enabled transform to use VersionSpec and call matches() with
match_none=False so unversioned components don't match version specs
- Update enable()/disable() signatures to accept VersionSpec
- Add comprehensive tests for version range matching
Examples:
- disable(version=VersionSpec(eq="v2")) - disable only v2
- disable(version=VersionSpec(gte="v2")) - disable v2 and later
- disable(version=VersionSpec(gte="v1", lt="v3")) - disable v1, v2
Rename Visibility to Enabled, collapse VisibilityRule into the transform,
and move enabled filtering from Provider to Server level so server-level
transforms can override provider-level disables.