fix: unblock the MCP session, and authenticate to the AI gateway with OIDC

Two things, both discovered by instrumenting the failure rather than trusting
the message it printed.

The MCP session never failed on protocol. macOS resolves any `.local` name
through mDNS first and takes a flat ~5.0s doing it, which sits exactly on
httpx's 5s default connect timeout; fastmcp reported the resulting
ConnectTimeout only as "Failed to initialize server session". Earlier tests
used `curl --resolve`, which bypasses DNS entirely and made it look like DNS
had been eliminated. Settings.http_timeout (60s) now covers the shared client,
the client fastmcp builds, and MCPToolset's init_timeout. list-products,
get-product and create-product now enumerate over the real gateway.

Inference used a static "unused" api_key against a gateway whose
openid-connect plugin requires audience ["llm-proxy"]. The model call now
carries a token exchanged for llm-proxy-audience, so it is attributable to the
same (agent, end user) pair as every tool call.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Stéphane Tailland
2026-08-28 11:01:18 +02:00
parent 4fd6e21a40
commit 0bc06512f5
6 changed files with 59 additions and 6 deletions
+13
View File
@@ -14,7 +14,9 @@ issues #172/#194/#235, CrewAI's MCPServerAdapter) has the same answer.
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
import httpx
from pydantic_ai.mcp import MCPToolset, StreamableHttpTransport
from .config import Settings
@@ -55,6 +57,15 @@ async def build_toolsets(
injection that talks the agent into reaching the wrong domain fails at
Kong rather than succeeding quietly.
"""
def client_factory(**kwargs: Any) -> httpx.AsyncClient:
# fastmcp builds this client itself and would otherwise take httpx's
# 5s default connect timeout. See Settings.http_timeout for why that is
# not survivable against `.local` names, and what the failure looks like
# when it bites: a bare "Failed to initialize server session".
kwargs["verify"] = settings.verify
kwargs["timeout"] = httpx.Timeout(settings.http_timeout, connect=settings.http_timeout)
return httpx.AsyncClient(**kwargs)
toolsets: list[MCPToolset] = []
for target in targets(settings):
token = await exchanger.exchange(subject_token, target.audience_scope)
@@ -64,8 +75,10 @@ async def build_toolsets(
url=target.url,
headers={"Authorization": f"Bearer {token}"},
verify=settings.verify,
httpx_client_factory=client_factory,
),
id=target.name,
init_timeout=settings.http_timeout,
)
)
return toolsets