mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-30 06:43:27 +00:00
* refactor(mcp): drop the experimental label from the MCP server and client paths Move litellm/proxy/_experimental/mcp_server to litellm/proxy/mcp_server and litellm/experimental_mcp_client to litellm/mcp_client, with the mirrored tests moved under tests/unit/proxy/mcp_server and tests/unit/mcp_client. Both old packages stay as shims that warn with DeprecationWarning, re-export the new package's public names and alias every submodule in sys.modules so deep imports keep resolving to the same objects. The OpenAPI docs gate now puts the merge base's enterprise package on PYTHONPATH so its base counts do not mix the checked-out enterprise code with the base litellm tree Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * ci(mcp): keep tests/unit/proxy/_experimental as a sharded root for the shim tests Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(mcp): alias namespace subpackages and bind attributes in the deprecated MCP shims pkgutil.walk_packages skipped mcp_server/auth because it has no __init__.py, so old imports of litellm.proxy._experimental.mcp_server.auth.* failed, and sys.modules entries alone left litellm.experimental_mcp_client.tools unreachable as an attribute. Walk the package on disk and setattr each alias on its old parent. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
146 lines
4.9 KiB
Python
146 lines
4.9 KiB
Python
import asyncio
|
|
import importlib
|
|
import os
|
|
|
|
import pytest
|
|
|
|
import litellm
|
|
from litellm.litellm_core_utils.logging_worker import GLOBAL_LOGGING_WORKER
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def event_loop():
|
|
try:
|
|
loop = asyncio.get_running_loop()
|
|
except RuntimeError:
|
|
loop = asyncio.new_event_loop()
|
|
yield loop
|
|
loop.close()
|
|
|
|
|
|
@pytest.fixture(scope="function", autouse=True)
|
|
def setup_and_teardown():
|
|
"""
|
|
This fixture reloads litellm before every function. To speed up testing by removing callbacks being chained.
|
|
"""
|
|
importlib.reload(litellm)
|
|
import asyncio
|
|
|
|
loop = asyncio.get_event_loop_policy().new_event_loop()
|
|
asyncio.set_event_loop(loop)
|
|
yield
|
|
|
|
# Teardown code (executes after the yield point)
|
|
# LoggingWorker carries still-queued coroutines onto the next test's loop, where they'd log into that test's callbacks
|
|
asyncio.run(GLOBAL_LOGGING_WORKER.clear_queue())
|
|
loop.close() # Close the loop created earlier
|
|
asyncio.set_event_loop(None) # Remove the reference to the loop
|
|
|
|
|
|
@pytest.fixture(scope="function", autouse=True)
|
|
async def drain_logging_worker():
|
|
"""
|
|
The logging queue is bound to the running loop, so anything left queued when a test's loop
|
|
goes away is carried onto the next test's loop and fires against its callbacks.
|
|
"""
|
|
from litellm.litellm_core_utils.logging_worker import GLOBAL_LOGGING_WORKER
|
|
|
|
yield
|
|
|
|
try:
|
|
await asyncio.wait_for(GLOBAL_LOGGING_WORKER.clear_queue(), timeout=10)
|
|
except asyncio.TimeoutError:
|
|
pass
|
|
|
|
|
|
def pytest_collection_modifyitems(config, items):
|
|
# Separate tests in 'test_amazing_proxy_custom_logger.py' and other tests
|
|
custom_logger_tests = [
|
|
item for item in items if "custom_logger" in item.parent.name
|
|
]
|
|
other_tests = [item for item in items if "custom_logger" not in item.parent.name]
|
|
|
|
# Sort tests based on their names
|
|
custom_logger_tests.sort(key=lambda x: x.name)
|
|
other_tests.sort(key=lambda x: x.name)
|
|
|
|
# Reorder the items list
|
|
items[:] = custom_logger_tests + other_tests
|
|
|
|
|
|
@pytest.fixture
|
|
def config_only_mcp_manager_factory():
|
|
from litellm.proxy.mcp_server.mcp_server_manager import MCPServerManager
|
|
|
|
class ConfigOnlyManager(MCPServerManager):
|
|
def initialize_tool_name_to_mcp_server_name_mapping(self):
|
|
return None
|
|
|
|
return ConfigOnlyManager
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _hermetic_mcp_server_registry():
|
|
from litellm.proxy.mcp_server.catalog import CatalogSnapshots
|
|
from litellm.proxy.mcp_server.mcp_server_manager import (
|
|
global_mcp_server_manager,
|
|
)
|
|
from litellm.proxy.mcp_server.tool_registry import global_mcp_tool_registry
|
|
|
|
saved_tools = global_mcp_tool_registry.published_tools
|
|
global_mcp_tool_registry.published_tools = {}
|
|
saved_catalog = global_mcp_server_manager.catalog
|
|
global_mcp_server_manager.catalog = CatalogSnapshots(global_mcp_server_manager)
|
|
saved_registry = dict(global_mcp_server_manager.registry)
|
|
saved_config_servers = dict(global_mcp_server_manager.config_mcp_servers)
|
|
saved_tool_mapping = dict(global_mcp_server_manager.tool_name_to_mcp_server_name_mapping)
|
|
saved_oauth_slots = global_mcp_server_manager._oauth_discovery_slots
|
|
global_mcp_server_manager.registry.clear()
|
|
global_mcp_server_manager.config_mcp_servers.clear()
|
|
global_mcp_server_manager.tool_name_to_mcp_server_name_mapping.clear()
|
|
global_mcp_server_manager._oauth_discovery_slots = ()
|
|
try:
|
|
yield
|
|
finally:
|
|
global_mcp_server_manager.registry.clear()
|
|
global_mcp_server_manager.registry.update(saved_registry)
|
|
global_mcp_server_manager.config_mcp_servers.clear()
|
|
global_mcp_server_manager.config_mcp_servers.update(saved_config_servers)
|
|
global_mcp_server_manager.tool_name_to_mcp_server_name_mapping.clear()
|
|
global_mcp_server_manager.tool_name_to_mcp_server_name_mapping.update(saved_tool_mapping)
|
|
global_mcp_server_manager._oauth_discovery_slots = saved_oauth_slots
|
|
global_mcp_server_manager.catalog = saved_catalog
|
|
global_mcp_tool_registry.published_tools = saved_tools
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _hermetic_server_root_path():
|
|
saved = os.environ.pop("SERVER_ROOT_PATH", None)
|
|
try:
|
|
yield
|
|
finally:
|
|
if saved is not None:
|
|
os.environ["SERVER_ROOT_PATH"] = saved
|
|
|
|
|
|
@pytest.fixture
|
|
def _mcp_request_ctx():
|
|
def _mcp_request_ctx(**overrides):
|
|
from types import SimpleNamespace
|
|
|
|
from mcp.server.context import ServerRequestContext
|
|
|
|
kwargs = {
|
|
"session": SimpleNamespace(),
|
|
"lifespan_context": {},
|
|
"protocol_version": "2025-06-18",
|
|
"method": "",
|
|
"params": None,
|
|
"request_id": 1,
|
|
"meta": None,
|
|
"request": None,
|
|
}
|
|
kwargs.update(overrides)
|
|
return ServerRequestContext(**kwargs)
|
|
|
|
return _mcp_request_ctx
|