fix(mcp): preserve BYOK discovery and isolate session authorization

This commit is contained in:
Joshua Valluru 2026-09-11 15:10:33 -07:00
parent 6d5c2d85ef
commit 731f79fa31
10 changed files with 413 additions and 34 deletions

View file

@ -18,7 +18,7 @@ import html as _html_module
import time
import uuid
from typing import Final, cast
from urllib.parse import urlencode
from urllib.parse import urlencode, urlparse
import jwt
from fastapi import APIRouter, Depends, Form, HTTPException, Request
@ -26,12 +26,12 @@ from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
from litellm._logging import verbose_proxy_logger
from litellm.proxy._experimental.mcp_server.db import store_user_credential
from litellm.proxy._experimental.mcp_server.discoverable_endpoints import (
get_request_base_url,
)
from litellm.proxy._experimental.mcp_server.oauth_utils import (
BYOK_RESOURCE_METADATA_PATH,
TOKEN_NO_CACHE_HEADERS,
get_request_base_url,
validate_loopback_redirect_uri,
well_known_root_suffix,
)
from litellm.proxy._types import UserAPIKeyAuth
@ -595,13 +595,10 @@ def _build_authorize_html(
# ---------------------------------------------------------------------------
@router.get("/.well-known/oauth-authorization-server", include_in_schema=False)
async def oauth_authorization_server_metadata(request: Request) -> JSONResponse:
"""RFC 8414 Authorization Server Metadata for the BYOK OAuth flow."""
base_url: Final = get_request_base_url(request)
def _byok_authorization_server_response(base_url: str, issuer: str) -> JSONResponse:
return JSONResponse(
{
"issuer": base_url,
"issuer": issuer,
"authorization_endpoint": f"{base_url}/v1/mcp/oauth/authorize",
"token_endpoint": f"{base_url}/v1/mcp/oauth/token",
"response_types_supported": ["code"],
@ -611,6 +608,30 @@ async def oauth_authorization_server_metadata(request: Request) -> JSONResponse:
)
@router.get("/.well-known/oauth-authorization-server", include_in_schema=False)
async def oauth_authorization_server_metadata(request: Request) -> JSONResponse:
base_url: Final = get_request_base_url(request)
return _byok_authorization_server_response(base_url, base_url)
@router.get(f"/.well-known/oauth-authorization-server{well_known_root_suffix()}/v1/mcp/oauth", include_in_schema=False)
async def byok_authorization_server_metadata(request: Request) -> JSONResponse:
base_url: Final = get_request_base_url(request)
return _byok_authorization_server_response(base_url, f"{base_url}/v1/mcp/oauth")
@router.get(BYOK_RESOURCE_METADATA_PATH, include_in_schema=False)
async def byok_protected_resource_metadata(request: Request) -> JSONResponse:
base_url: Final = get_request_base_url(request)
parsed: Final = urlparse(base_url)
return JSONResponse(
{
"resource": f"{parsed.scheme}://{parsed.netloc}",
"authorization_servers": (f"{base_url}/v1/mcp/oauth",),
}
)
# ---------------------------------------------------------------------------
# Authorization endpoint — GET (show form) and POST (process form)
# ---------------------------------------------------------------------------

View file

@ -1842,6 +1842,30 @@ async def register_client_with_server(
return JSONResponse(token_response)
@router.get("/authorize/mcp-session")
async def authorize_mcp_session(
request: Request,
redirect_uri: str,
client_id: str,
state: str = "",
code_challenge: str | None = None,
code_challenge_method: str | None = None,
response_type: str | None = None,
resource: str | None = None,
) -> Response:
return aggregate_authorize(
request=request,
client_id=client_id,
redirect_uri=redirect_uri,
state=state,
code_challenge=code_challenge,
code_challenge_method=code_challenge_method,
response_type=response_type,
session_user_id=_session_cookie_user_id(request),
resource=resource,
)
@router.get("/{mcp_server_name}/authorize")
@router.get("/authorize")
async def authorize(
@ -2538,13 +2562,13 @@ def _jwt_auth_issuers() -> list:
@router.get("/.well-known/oauth-protected-resource")
def oauth_protected_resource_root(request: Request) -> dict[str, str | list[str]]:
def oauth_protected_resource_root(request: Request) -> dict[str, str | tuple[str, ...]]:
request_base_url: Final = get_request_base_url(request)
parsed: Final = urlparse(request_base_url)
return {
"resource": f"{parsed.scheme}://{parsed.netloc}",
"authorization_servers": [f"{request_base_url}/mcp"],
"scopes_supported": [],
"authorization_servers": (f"{request_base_url}/mcp",),
"scopes_supported": (),
}
@ -2574,14 +2598,14 @@ def _build_aggregate_authorization_server_response(request: Request) -> dict:
The issuer is ``{base}/mcp`` and must stay equal to the value the
aggregate protected-resource document advertises: spec clients verify the
issuer in the metadata matches the one that derived the well-known URL.
Advertises the root /authorize, /token, and /register endpoints and
Advertises the MCP session authorize endpoint, root /token and /register endpoints, and
``token_endpoint_auth_methods_supported: ["none", ...]`` because DCR
clients (Claude Desktop, MCP Inspector) register as public clients; PKCE
S256 is mandatory in the gateway's authorize flow."""
request_base_url: Final = get_request_base_url(request)
return {
"issuer": f"{request_base_url}/mcp",
"authorization_endpoint": f"{request_base_url}/authorize",
"authorization_endpoint": f"{request_base_url}/authorize/mcp-session",
"token_endpoint": f"{request_base_url}/token",
"introspection_endpoint": f"{request_base_url}/introspect",
"registration_endpoint": f"{request_base_url}/register",

View file

@ -91,6 +91,7 @@ from litellm.proxy._experimental.mcp_server.oauth2_token_cache import (
from litellm.proxy._experimental.mcp_server.oauth_utils import (
_redact_mcp_resource_url,
canonicalize_url_identity,
get_byok_www_authenticate,
)
from litellm.proxy._experimental.mcp_server.outbound_credentials import (
Error,
@ -1232,7 +1233,7 @@ async def _resolve_byok_mcp_auth_header(
"Complete the OAuth authorization flow to provide your API key."
),
},
headers={"WWW-Authenticate": 'Bearer resource_metadata="/.well-known/oauth-protected-resource"'},
headers={"WWW-Authenticate": get_byok_www_authenticate()},
)
return byok_cred

View file

@ -126,6 +126,14 @@ def _resolve_proxy_base_url_env() -> str | None:
return None
BYOK_RESOURCE_METADATA_PATH: Final = "/v1/mcp/oauth/protected-resource"
def get_byok_www_authenticate() -> str:
base_url: Final = _resolve_proxy_base_url_env() or well_known_root_suffix()
return f'Bearer resource_metadata="{base_url}{BYOK_RESOURCE_METADATA_PATH}"'
def get_request_base_url(request: Request) -> str:
"""
Get the base URL for the request, considering X-Forwarded-* headers.

View file

@ -56,6 +56,7 @@ from litellm.proxy._experimental.mcp_server.mcp_debug import (
)
from litellm.proxy._experimental.mcp_server.oauth_utils import (
_redact_mcp_resource_url,
get_byok_www_authenticate,
get_passthrough_www_authenticate,
get_route_relative_request_path,
well_known_root_suffix,
@ -2852,7 +2853,7 @@ if MCP_AVAILABLE:
"server_name": mcp_server.server_name or mcp_server.name,
"message": "User identity is required for BYOK servers",
},
headers={"WWW-Authenticate": 'Bearer resource_metadata="/.well-known/oauth-protected-resource"'},
headers={"WWW-Authenticate": get_byok_www_authenticate()},
)
# Check shared credential cache before hitting the DB.
@ -2873,9 +2874,7 @@ if MCP_AVAILABLE:
"Complete the OAuth authorization flow to provide your API key."
),
},
headers={
"WWW-Authenticate": 'Bearer resource_metadata="/.well-known/oauth-protected-resource"'
},
headers={"WWW-Authenticate": get_byok_www_authenticate()},
)
return
@ -2914,7 +2913,7 @@ if MCP_AVAILABLE:
"Complete the OAuth authorization flow to provide your API key."
),
},
headers={"WWW-Authenticate": 'Bearer resource_metadata="/.well-known/oauth-protected-resource"'},
headers={"WWW-Authenticate": get_byok_www_authenticate()},
)
async def execute_mcp_tool(
@ -3068,9 +3067,7 @@ if MCP_AVAILABLE:
"Complete the OAuth authorization flow to provide your API key."
),
},
headers={
"WWW-Authenticate": 'Bearer resource_metadata="/.well-known/oauth-protected-resource"'
},
headers={"WWW-Authenticate": get_byok_www_authenticate()},
)
mcp_auth_header = byok_cred
elif mcp_server.is_byok:

View file

@ -25026,6 +25026,129 @@
]
}
},
"/authorize/mcp-session": {
"get": {
"operationId": "authorize_mcp_session_authorize_mcp_session_get",
"parameters": [
{
"in": "query",
"name": "redirect_uri",
"required": true,
"schema": {
"title": "Redirect Uri",
"type": "string"
}
},
{
"in": "query",
"name": "client_id",
"required": true,
"schema": {
"title": "Client Id",
"type": "string"
}
},
{
"in": "query",
"name": "state",
"required": false,
"schema": {
"default": "",
"title": "State",
"type": "string"
}
},
{
"in": "query",
"name": "code_challenge",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Code Challenge"
}
},
{
"in": "query",
"name": "code_challenge_method",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Code Challenge Method"
}
},
{
"in": "query",
"name": "response_type",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Response Type"
}
},
{
"in": "query",
"name": "resource",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Resource"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Authorize Mcp Session",
"tags": [
"mcp_discoverable"
]
}
},
"/callback": {
"get": {
"description": "OAuth 2.0 authorization response handler for MCP loopback clients.\n\nAccepts either:\n\n- A successful authorization response (``code`` + ``state``), which is\n forwarded back to the validated client ``redirect_uri`` with the\n original (un-wrapped) ``state``.\n- An error response (``error``[+``error_description``/``error_uri``]), per\n RFC 6749 \u00a74.1.2.1. When ``state`` is present and decodes to a trusted\n ``redirect_uri``, the error params are propagated back to the client so\n its OAuth library can surface them. Otherwise we render an HTML error\n page so the user is not left on an opaque 422 / blank screen.",

View file

@ -97,6 +97,51 @@ def unauthenticated_client():
# ---------------------------------------------------------------------------
@pytest.mark.parametrize("byok_first", [True, False])
def test_byok_challenge_discovers_api_key_flow(monkeypatch, byok_first):
from litellm.proxy._experimental.mcp_server import byok_oauth_endpoints, discoverable_endpoints
from litellm.proxy._experimental.mcp_server.oauth_utils import get_byok_www_authenticate
monkeypatch.delenv("PROXY_BASE_URL", raising=False)
monkeypatch.delenv("SERVER_ROOT_PATH", raising=False)
app = FastAPI()
routers = (byok_oauth_endpoints.router, discoverable_endpoints.router)
for item in routers if byok_first else reversed(routers):
app.include_router(item)
with TestClient(app) as session:
challenge = get_byok_www_authenticate()
assert challenge == 'Bearer resource_metadata="/v1/mcp/oauth/protected-resource"'
response = session.get(challenge.split('"')[1])
assert response.status_code == 200
assert response.json() == {
"resource": "http://testserver",
"authorization_servers": ["http://testserver/v1/mcp/oauth"],
}
authorization = session.get("/.well-known/oauth-authorization-server/v1/mcp/oauth")
assert authorization.status_code == 200
metadata = authorization.json()
assert metadata["issuer"] == response.json()["authorization_servers"][0]
assert metadata["authorization_endpoint"] == "http://testserver/v1/mcp/oauth/authorize"
assert metadata["token_endpoint"] == "http://testserver/v1/mcp/oauth/token"
assert metadata["code_challenge_methods_supported"] == ["S256"]
@pytest.mark.parametrize(
("base_url", "root_path", "expected"),
[
("", "", "/v1/mcp/oauth/protected-resource"),
("", "/proxy", "/proxy/v1/mcp/oauth/protected-resource"),
("https://gateway.example.com/proxy", "/proxy", "https://gateway.example.com/proxy/v1/mcp/oauth/protected-resource"),
],
)
def test_byok_challenge_preserves_external_base(monkeypatch, base_url, root_path, expected):
from litellm.proxy._experimental.mcp_server.oauth_utils import get_byok_www_authenticate
monkeypatch.setenv("PROXY_BASE_URL", base_url)
monkeypatch.setenv("SERVER_ROOT_PATH", root_path)
assert get_byok_www_authenticate() == f'Bearer resource_metadata="{expected}"'
def test_oauth_authorization_server_metadata(client):
resp = client.get("/.well-known/oauth-authorization-server")
assert resp.status_code == 200
@ -492,7 +537,7 @@ async def test_check_byok_credential_no_user_id():
@pytest.mark.asyncio
async def test_check_byok_credential_missing_credential():
async def test_check_byok_credential_missing_credential(monkeypatch):
"""BYOK server with a known user but no stored credential → 401."""
from litellm.proxy._experimental.mcp_server.server import _check_byok_credential
from litellm.proxy._types import UserAPIKeyAuth
@ -506,6 +551,11 @@ async def test_check_byok_credential_missing_credential():
)
user_auth = UserAPIKeyAuth(user_id="user-99", api_key="sk-test")
from litellm.proxy._experimental.mcp_server import server as server_module
monkeypatch.delenv("PROXY_BASE_URL", raising=False)
monkeypatch.delenv("SERVER_ROOT_PATH", raising=False)
monkeypatch.setattr(server_module, "_byok_cred_cache", {})
mock_prisma = MagicMock()
with (
@ -517,6 +567,10 @@ async def test_check_byok_credential_missing_credential():
):
with pytest.raises(HTTPException) as exc_info:
await _check_byok_credential(server, user_auth)
with pytest.raises(HTTPException) as cached_exc:
await _check_byok_credential(server, user_auth)
assert cached_exc.value.status_code == 401
assert cached_exc.value.headers == exc_info.value.headers
assert exc_info.value.status_code == 401
detail: Any = exc_info.value.detail
@ -524,7 +578,38 @@ async def test_check_byok_credential_missing_credential():
assert detail["server_id"] == "byok-2"
headers = exc_info.value.headers or {}
assert "WWW-Authenticate" in headers # type: ignore[operator]
assert "oauth-protected-resource" in headers["WWW-Authenticate"] # type: ignore[index]
assert headers["WWW-Authenticate"] == 'Bearer resource_metadata="/v1/mcp/oauth/protected-resource"'
@pytest.mark.asyncio
async def test_execute_byok_tool_missing_credential_advertises_api_key_flow(monkeypatch):
from datetime import datetime, timezone
from litellm.proxy._experimental.mcp_server import server as mcp_module
from litellm.proxy import proxy_server
from litellm.proxy._types import UserAPIKeyAuth
from litellm.types.mcp_server.mcp_server_manager import MCPServer
monkeypatch.setenv("PROXY_BASE_URL", "https://gateway.example.com/proxy")
monkeypatch.setattr(mcp_module, "_byok_cred_cache", {})
server = MCPServer(server_id="byok-discovery", name="byok-discovery", transport=MCPTransport.http, is_byok=True)
prisma = MagicMock()
prisma.db.litellm_mcpusercredentials.find_unique = AsyncMock(return_value=None)
monkeypatch.setattr(proxy_server, "prisma_client", prisma)
with pytest.raises(HTTPException) as exc_info:
await mcp_module.execute_mcp_tool(
name="list_regions",
arguments={},
allowed_mcp_servers=[server],
requested_server_id=server.server_id,
start_time=datetime.now(timezone.utc),
user_api_key_auth=UserAPIKeyAuth(user_id="byok-discovery-user"),
)
assert exc_info.value.status_code == 401
assert exc_info.value.detail["server_id"] == server.server_id
assert exc_info.value.headers == {
"WWW-Authenticate": 'Bearer resource_metadata="https://gateway.example.com/proxy/v1/mcp/oauth/protected-resource"'
}
@pytest.mark.asyncio

View file

@ -3485,7 +3485,7 @@ def test_root_protected_resource_discovers_gateway(monkeypatch, server_count, by
assert authorization.status_code == 200
metadata = authorization.json()
assert metadata["issuer"] == response.json()["authorization_servers"][0]
assert metadata["authorization_endpoint"] == f"{base_url}/authorize"
assert metadata["authorization_endpoint"] == f"{base_url}/authorize/mcp-session"
assert metadata["token_endpoint"] == f"{base_url}/token"
assert metadata["registration_endpoint"] == f"{base_url}/register"
aggregate = client.get("/.well-known/oauth-protected-resource/mcp")
@ -3508,8 +3508,8 @@ async def test_unnamed_protected_resource_builder_uses_gateway_origin(monkeypatc
response = await _build_oauth_protected_resource_response(request, None, False)
assert response == {
"resource": "https://gateway.example.com",
"authorization_servers": ["https://gateway.example.com/mcp"],
"scopes_supported": [],
"authorization_servers": ("https://gateway.example.com/mcp",),
"scopes_supported": (),
}
@ -4170,9 +4170,9 @@ async def test_discovery_root_does_not_expose_private_server_for_external_client
assert "/test_oauth/" not in authorization_response["authorization_endpoint"]
assert "/test_oauth/" not in authorization_response["token_endpoint"]
assert authorization_response["scopes_supported"] == []
assert resource_response["authorization_servers"] == ["https://llm.example.com/mcp"]
assert tuple(resource_response["authorization_servers"]) == ("https://llm.example.com/mcp",)
assert resource_response["resource"] == "https://llm.example.com"
assert resource_response["scopes_supported"] == []
assert not resource_response["scopes_supported"]
finally:
global_mcp_server_manager.registry.clear()
@ -9048,7 +9048,7 @@ def test_aggregate_wellknown_routes_serve_gateway_metadata():
assert asm.status_code == 200
assert asm.json()["issuer"] == "http://testserver/mcp"
assert asm.json()["authorization_endpoint"] == "http://testserver/authorize"
assert asm.json()["authorization_endpoint"] == "http://testserver/authorize/mcp-session"
assert "none" in asm.json()["token_endpoint_auth_methods_supported"]
@ -9137,7 +9137,7 @@ async def test_root_resource_uses_gateway_without_changing_authorization_relay()
)
assert "/test_oauth/authorize" in authorization_response["authorization_endpoint"]
assert authorization_response["issuer"] == "https://llm.example.com"
assert resource_response["authorization_servers"] == ["https://llm.example.com/mcp"]
assert tuple(resource_response["authorization_servers"]) == ("https://llm.example.com/mcp",)
assert resource_response["resource"] == "https://llm.example.com"
finally:
global_mcp_server_manager.registry.clear()
@ -10670,7 +10670,7 @@ class TestPerRequestRootPathDiscovery:
assert asm.status_code == 200
assert asm.json()["issuer"] == "http://testserver/tenant-a/mcp"
assert asm.json()["authorization_endpoint"] == "http://testserver/tenant-a/authorize"
assert asm.json()["authorization_endpoint"] == "http://testserver/tenant-a/authorize/mcp-session"
# The prefixed authorize URL routes to the real handler (not 404):
# under per-request root_path the whole app is reachable per-prefix,
@ -10829,6 +10829,68 @@ def _consent_flow_handle(page: str) -> str:
return match.group(1)
@pytest.mark.parametrize("redirect_uri", ["http://127.0.0.1:51234/callback", "https://client.example.com/callback"])
@pytest.mark.parametrize("signed_in", [True, False])
def test_root_discovery_origin_authorizes_mcp_session(monkeypatch, redirect_uri, signed_in):
from urllib.parse import parse_qs, urlparse
client, session_cookie, minted = _native_client_app(monkeypatch)
root = client.get("/.well-known/oauth-protected-resource")
assert root.status_code == 200
assert root.json()["resource"] == "http://testserver"
authorization = client.get("/.well-known/oauth-authorization-server/mcp")
assert authorization.status_code == 200
metadata = authorization.json()
registered = client.post(metadata["registration_endpoint"], json={"redirect_uris": [redirect_uri]})
assert registered.status_code == 201
if signed_in:
client.cookies.set("token", session_cookie)
response = client.get(
metadata["authorization_endpoint"],
params={
"response_type": "code",
"client_id": registered.json()["client_id"],
"redirect_uri": redirect_uri,
"state": "mcp-state",
"code_challenge": _s256("v" * 43),
"code_challenge_method": "S256",
"resource": root.json()["resource"],
},
follow_redirects=False,
)
assert response.status_code == 303
target = urlparse(response.headers["location"])
assert target.path == ("/ui/connect" if signed_in else "/sso/key/generate")
if signed_in:
flow = parse_qs(target.query)["connect_flow"][0]
described = client.get("/authorize/flow", params={"flow": flow})
assert described.status_code == 200
assert described.json()["state"] == "unscoped"
assert minted == []
@pytest.mark.parametrize("valid_client", [True, False])
def test_mcp_session_authorize_rejects_invalid_registration_or_pkce(monkeypatch, valid_client):
client, session_cookie, minted = _native_client_app(monkeypatch)
redirect_uri = "https://client.example.com/callback"
registered = client.post("/register", json={"redirect_uris": [redirect_uri]})
assert registered.status_code == 201
client.cookies.set("token", session_cookie)
response = client.get(
"/authorize/mcp-session",
params={
"client_id": registered.json()["client_id"] if valid_client else "unknown-client",
"redirect_uri": redirect_uri,
"response_type": "code",
},
follow_redirects=False,
)
assert response.status_code == 400
assert response.json()["error"] == ("invalid_request" if valid_client else "invalid_client")
assert "location" not in response.headers
assert minted == []
def test_native_client_login_walks_discovery_consent_token_refresh_and_revoke(monkeypatch):
"""The whole ``lite login --pkce`` server side over the real router: a Go CLI reads the versioned
discovery document, registers a loopback public client, the signed-in user consents to a team,

View file

@ -1233,13 +1233,14 @@ class TestResolveByokMcpAuthHeader:
assert result == "stored-cred"
@pytest.mark.asyncio
async def test_byok_server_raises_401_when_no_credential_stored(self):
async def test_byok_server_raises_401_when_no_credential_stored(self, monkeypatch):
from fastapi import HTTPException
from litellm.proxy._experimental.mcp_server.mcp_server_manager import (
_resolve_byok_mcp_auth_header,
)
monkeypatch.setenv("PROXY_BASE_URL", "https://gateway.example.com/proxy")
server = self._server(is_byok=True)
user_auth = UserAPIKeyAuth(user_id="user-1", api_key="sk-dashboard")
@ -1252,6 +1253,9 @@ class TestResolveByokMcpAuthHeader:
assert exc_info.value.status_code == 401
assert exc_info.value.detail["error"] == "byok_auth_required"
assert exc_info.value.headers == {
"WWW-Authenticate": 'Bearer resource_metadata="https://gateway.example.com/proxy/v1/mcp/oauth/protected-resource"'
}
@pytest.mark.asyncio
async def test_byok_server_checks_credential_and_keeps_caller_header_when_supplied(self):

View file

@ -1177,6 +1177,23 @@ export interface paths {
patch?: never;
trace?: never;
};
"/authorize/mcp-session": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Authorize Mcp Session */
get: operations["authorize_mcp_session_authorize_mcp_session_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/auto_router/benchmarks": {
parameters: {
query?: never;
@ -41648,6 +41665,43 @@ export interface operations {
};
};
};
authorize_mcp_session_authorize_mcp_session_get: {
parameters: {
query: {
redirect_uri: string;
client_id: string;
state?: string;
code_challenge?: string | null;
code_challenge_method?: string | null;
response_type?: string | null;
resource?: string | null;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": unknown;
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
get_auto_router_benchmarks_auto_router_benchmarks_get: {
parameters: {
query?: {