Skip to content

MCPToolset(url, http_client=...) crashes: factory missing follow_redirects kwarg passed by FastMCP #5688

@voorhs

Description

@voorhs

AI notice: this bug was spotted and this report was generated by ANthropic's Claude Opus 4.7

Initial Checks

Description

Constructing an HTTP MCPToolset with an explicit http_client= fails at connect
time with a TypeError about an unexpected follow_redirects keyword argument.
The URL-only path (MCPToolset('http://...')) is unaffected.

Root cause

When http_client= is passed, MCPToolset adapts it to a factory:

  • pydantic_ai/mcp.py (~L2322): factory = _make_httpx_client_factory(http_client),
    passed as httpx_client_factory= to the FastMCP transport.
  • _make_httpx_client_factory (~L2344) returns a closure with signature
    factory(headers=None, timeout=None, auth=None). This matches the mcp SDK's
    McpHttpClientFactory protocol (mcp/shared/_httpx_utils.py), which declares
    exactly headers, timeout, auth.

But the actual caller is FastMCP, not the mcp SDK directly. In
fastmcp/client/transports/http.py (~L176, fastmcp 3.3.1) it calls:

http_client = self.httpx_client_factory(
    headers=headers,
    auth=self.auth,
    follow_redirects=True,  # type: ignore[call-arg]  # ty:ignore[unknown-argument]
    **({"timeout": timeout} if timeout else {}),
)

i.e. FastMCP passes follow_redirects= (intentionally — note its own
# type: ignore[call-arg]), which is beyond the McpHttpClientFactory protocol the
pydantic-ai closure was written against. The closure has no **kwargs, so it raises.

Suggested fix

Make the closure tolerant of extra kwargs the transport may pass (its return type is
already Callable[..., httpx.AsyncClient]):

def factory(
    headers: dict[str, str] | None = None,
    timeout: httpx.Timeout | None = None,
    auth: httpx.Auth | None = None,
    **_kwargs: Any,
) -> httpx.AsyncClient:
    return http_client

This is robust regardless of whether the module is on httpx or httpx2.

Notes

  • Related but not a fix: v2 prep: prefer httpx2 in pydantic_ai.mcp, warn on httpx fallback #5664 (prefer httpx2 in pydantic_ai.mcp) touches the same
    module but does not modify _make_httpx_client_factory.
  • Arguably FastMCP is calling factories outside the documented McpHttpClientFactory
    protocol; but since MCPToolset is built on FastMCP's client, accepting **kwargs
    here is the pragmatic and forward-compatible fix.

Minimal, Reproducible Example

No live server required — it fails while building the httpx client, before any request:

import asyncio
import httpx
from pydantic_ai.mcp import MCPToolset

async def main():
    toolset = MCPToolset("http://localhost:9999/mcp", http_client=httpx.AsyncClient())
    async with toolset:
        await toolset.list_tools()

asyncio.run(main())

Actual behavior

RuntimeError: Client failed to connect: _make_httpx_client_factory.<locals>.factory()
got an unexpected keyword argument 'follow_redirects'

Expected behavior

The user-supplied http_client is used for the connection (it works on the
MCPServerStreamableHTTP(url, http_client=...) deprecated path).

Python, Pydantic AI & LLM client version

  • pydantic-ai 1.102.0
  • fastmcp 3.3.1
  • httpx 0.28.1
  • Python 3.14.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    MCPbugReport that something isn't working, or PR implementing a fixhttpxpydanty:bugManaged by pydanty dogfooding automation

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions