#!/usr/bin/env python3 """ Standalone script to test tool allowlist enforcement and tool name extraction. Run from repo root: poetry run python scripts/test_tool_allowlist_script.py Or run the unit tests: poetry run pytest tests/test_litellm/proxy/test_tools_allowlist_enforcement.py -v """ import asyncio import sys from pathlib import Path # Ensure repo root is on path repo_root = Path(__file__).resolve().parent.parent if str(repo_root) not in sys.path: sys.path.insert(0, str(repo_root)) def test_extraction(): """Test extract_request_tool_names for each API shape.""" from litellm.proxy.guardrails.tool_name_extraction import extract_request_tool_names cases = [ ( "OpenAI chat tools", "/v1/chat/completions", {"tools": [{"type": "function", "function": {"name": "get_weather"}}]}, ), ( "OpenAI chat functions", "/v1/chat/completions", {"functions": [{"name": "run_sql"}]}, ), ( "OpenAI responses function", "/v1/responses", {"tools": [{"type": "function", "name": "get_current_weather"}]}, ), ( "OpenAI responses MCP", "/v1/responses", {"tools": [{"type": "mcp", "server_label": "dmcp"}]}, ), ( "Anthropic", "/v1/messages", {"tools": [{"name": "get_weather"}, {"name": "run_sql"}]}, ), ( "Google generateContent", "/generate_content", {"tools": [{"functionDeclarations": [{"name": "schedule_meeting"}]}]}, ), ("MCP call_tool", "/mcp/call_tool", {"name": "my_tool", "arguments": {}}), ( "Non-tool route", "/v1/embeddings", {"tools": [{"type": "function", "function": {"name": "x"}}]}, ), ] print("=== extract_request_tool_names(route, data) ===\n") for label, route, data in cases: names = extract_request_tool_names(route, data) print(f" {label}: {names}") print() async def test_check_tools_allowlist(): """Test check_tools_allowlist with mock tokens.""" from litellm.proxy._types import ProxyErrorTypes, ProxyException, UserAPIKeyAuth from litellm.proxy.auth.auth_checks import check_tools_allowlist def token(metadata=None, team_metadata=None): return UserAPIKeyAuth( api_key="test-key", user_id="user", team_id="team", org_id=None, models=["*"], metadata=metadata or {}, team_metadata=team_metadata or {}, ) print("=== check_tools_allowlist (auth) ===\n") # No allowlist -> pass await check_tools_allowlist( request_body={ "tools": [{"type": "function", "function": {"name": "get_weather"}}] }, valid_token=token(), team_object=None, route="/v1/chat/completions", ) print(" No allowlist, body has tools: PASS") # Allowed tool -> pass await check_tools_allowlist( request_body={ "tools": [{"type": "function", "function": {"name": "get_weather"}}] }, valid_token=token(metadata={"allowed_tools": ["get_weather"]}), team_object=None, route="/v1/chat/completions", ) print(" allowed_tools=['get_weather'], body has get_weather: PASS") # Disallowed tool -> raise try: await check_tools_allowlist( request_body={ "tools": [{"type": "function", "function": {"name": "get_weather"}}] }, valid_token=token(metadata={"allowed_tools": ["other_tool"]}), team_object=None, route="/v1/chat/completions", ) print(" DISALLOWED: expected ProxyException") except ProxyException as e: if e.type == ProxyErrorTypes.tool_access_denied: print( " allowed_tools=['other_tool'], body has get_weather: PASS (raised tool_access_denied)" ) else: print(f" Unexpected ProxyException type: {e.type}") except Exception as e: print(f" Unexpected: {e}") # Team allowlist when key empty await check_tools_allowlist( request_body={ "tools": [{"type": "function", "function": {"name": "get_weather"}}] }, valid_token=token(team_metadata={"allowed_tools": ["get_weather"]}), team_object=None, route="/v1/chat/completions", ) print(" team_metadata.allowed_tools=['get_weather']: PASS") print() def main(): print("Tool allowlist / tool name extraction – script checks\n") test_extraction() asyncio.run(test_check_tools_allowlist()) print("Done. For full unit tests run:") print( " poetry run pytest tests/test_litellm/proxy/test_tools_allowlist_enforcement.py -v" ) if __name__ == "__main__": main()