changing expires_in default to use actual slack return details (#29951)

This commit is contained in:
tin-berri 2026-06-08 18:13:06 -07:00 committed by GitHub
parent 1bbaf1c39d
commit 92817cb65b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 76 additions and 3 deletions

View File

@ -512,12 +512,13 @@ async def exchange_token_with_server(
result = {
"access_token": access_token,
"token_type": token_response.get("token_type", "Bearer"),
"expires_in": token_response.get("expires_in", 3600),
}
if "refresh_token" in token_response and token_response["refresh_token"]:
if token_response.get("expires_in") is not None:
result["expires_in"] = token_response["expires_in"]
if token_response.get("refresh_token"):
result["refresh_token"] = token_response["refresh_token"]
if "scope" in token_response and token_response["scope"]:
if token_response.get("scope"):
result["scope"] = token_response["scope"]
# RFC 6749 §5.1: token responses must not be cached.

View File

@ -1,5 +1,6 @@
"""Tests for MCP OAuth discoverable endpoints"""
import json
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
@ -2661,3 +2662,74 @@ async def test_token_endpoint_sets_no_store_cache_control():
assert response.headers["cache-control"] == "no-store"
assert response.headers["pragma"] == "no-cache"
async def _exchange_with_upstream_token_response(upstream_body):
from fastapi import Request
from litellm.proxy._experimental.mcp_server.discoverable_endpoints import (
exchange_token_with_server,
)
from litellm.proxy._types import MCPTransport
from litellm.types.mcp import MCPAuth
from litellm.types.mcp_server.mcp_server_manager import MCPServer
server = MCPServer(
server_id="t",
name="t",
server_name="t",
alias="t",
transport=MCPTransport.http,
auth_type=MCPAuth.oauth2,
client_id="cid",
client_secret="cs",
authorization_url="https://provider.com/oauth/authorize",
token_url="https://provider.com/oauth/token",
)
mock_request = MagicMock(spec=Request)
mock_request.base_url = "https://litellm.example.com/"
mock_request.headers = {}
fake_http_response = MagicMock()
fake_http_response.json.return_value = upstream_body
fake_http_response.raise_for_status = MagicMock()
fake_http_client = MagicMock()
fake_http_client.post = AsyncMock(return_value=fake_http_response)
with patch(
"litellm.proxy._experimental.mcp_server.discoverable_endpoints.get_async_httpx_client",
return_value=fake_http_client,
):
response = await exchange_token_with_server(
request=mock_request,
mcp_server=server,
grant_type="authorization_code",
code="c",
redirect_uri="http://127.0.0.1:3000/cb",
client_id="cid",
client_secret=None,
code_verifier=None,
)
return json.loads(response.body)
@pytest.mark.asyncio
async def test_token_exchange_omits_expires_in_when_upstream_omits_it():
"""A provider that issues a non-expiring token (e.g. Slack without token
rotation) returns no ``expires_in``. The exchange must mirror that and omit
``expires_in`` rather than fabricate a 1-hour TTL, so the stored credential
is treated as non-expiring instead of dying after an hour."""
body = await _exchange_with_upstream_token_response(
{"access_token": "tok", "token_type": "Bearer"}
)
assert "expires_in" not in body
@pytest.mark.asyncio
async def test_token_exchange_passes_through_upstream_expires_in():
"""When the provider does send ``expires_in`` (e.g. Slack with token
rotation), the exchange forwards the real value unchanged."""
body = await _exchange_with_upstream_token_response(
{"access_token": "tok", "token_type": "Bearer", "expires_in": 43200}
)
assert body["expires_in"] == 43200