Files
litellm/scripts/test_agent_mcp_endpoints.sh
T
Krish Dholakia 12c4876891 Agents - assign tools (#22064)
* feat(proxy): add max_iterations limiter for agent session loops (#22058)

Adds a new proxy hook that enforces a per-session cap on the number of
LLM calls an agentic loop can make. Callers send a session_id with each
request, and the hook counts calls per session, returning 429 when the
configured max_iterations limit is exceeded.

- Uses Redis Lua script for atomic increment (multi-instance safe)
- Falls back to in-memory cache when Redis unavailable
- Follows parallel_request_limiter_v3 pattern
- Configurable via key metadata: {"max_iterations": 25}
- Session counters auto-expire via TTL (default 1hr)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add new code execution dataset

* feat(agent_endpoints/): allow giving agents keys

* fix: ui fixes

* feat: allow assigning mcp servers to agents

* fix: eliminate duplicate DB queries in MCP agent auth and N+1 in agent listing (#22110)

- Extract _get_agent_object_permission helper so _get_allowed_mcp_servers_for_agent
  and _get_agent_tool_permissions_for_server share a single DB fetch instead of
  each independently querying the same agent row (was 1+N queries per MCP request)
- Use include={"object_permission": True} on find_many in get_all_agents_from_db
  to eagerly load permissions in one query instead of N+1
- Use include={"object_permission": True} on create/update/find_unique in all
  agent CRUD operations, removing attach_object_permission_to_dict follow-up calls

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:44:30 -08:00

187 lines
6.9 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Test agent endpoint-level changes for MCP tool permissions (object_permission).
# Requires: proxy running, valid admin API key, curl, jq.
#
# Usage:
# export LITELLM_PROXY_BASE_URL="http://localhost:4000" # optional, default below
# export LITELLM_API_KEY="sk-..." # required
# ./scripts/test_agent_mcp_endpoints.sh
#
set -euo pipefail
BASE_URL="${LITELLM_PROXY_BASE_URL:-http://localhost:4000}"
API_KEY="${LITELLM_API_KEY:-}"
if ! command -v jq &>/dev/null; then
echo "Error: jq is required. Install with: brew install jq (macOS) or apt install jq (Linux)"
exit 1
fi
if [[ -z "$API_KEY" ]]; then
echo "Error: LITELLM_API_KEY is not set. Export it or pass via env."
exit 1
fi
AUTH_HEADER="Authorization: Bearer $API_KEY"
AGENT_NAME="test-agent-mcp-$(date +%s)"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
pass() { echo -e "${GREEN}PASS${NC}: $*"; }
fail() { echo -e "${RED}FAIL${NC}: $*"; exit 1; }
info() { echo -e "${YELLOW}INFO${NC}: $*"; }
# --- 1. Create agent with object_permission ---
info "Creating agent with object_permission (mcp_servers, mcp_tool_permissions)..."
CREATE_RESP=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/v1/agents" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{
"agent_name": "'"$AGENT_NAME"'",
"agent_card_params": {
"protocolVersion": "1.0",
"name": "Test MCP Agent",
"description": "Agent for endpoint tests",
"url": "http://localhost:9999/",
"version": "1.0.0",
"defaultInputModes": ["text"],
"defaultOutputModes": ["text"],
"capabilities": {"streaming": true},
"skills": []
},
"object_permission": {
"mcp_servers": ["server_1", "server_2"],
"mcp_access_groups": ["group_a"],
"mcp_tool_permissions": {"server_1": ["tool_a", "tool_b"], "server_2": ["tool_c"]}
}
}')
HTTP_CODE=$(echo "$CREATE_RESP" | tail -n1)
BODY=$(echo "$CREATE_RESP" | sed '$d')
if [[ "$HTTP_CODE" != "200" ]]; then
fail "POST /v1/agents returned $HTTP_CODE. Body: $BODY"
fi
AGENT_ID=$(echo "$BODY" | jq -r '.agent_id')
if [[ -z "$AGENT_ID" || "$AGENT_ID" == "null" ]]; then
fail "POST /v1/agents did not return agent_id. Body: $BODY"
fi
pass "Created agent $AGENT_ID"
# Check create response includes object_permission
OP=$(echo "$BODY" | jq '.object_permission')
if [[ "$OP" == "null" || -z "$OP" ]]; then
fail "POST /v1/agents response missing object_permission. Body: $BODY"
fi
SERVERS=$(echo "$OP" | jq -r '.mcp_servers | join(",")')
if [[ "$SERVERS" != "server_1,server_2" ]]; then
fail "object_permission.mcp_servers unexpected: $SERVERS"
fi
pass "Create response includes object_permission with mcp_servers and mcp_tool_permissions"
# --- 2. GET /v1/agents (list) includes object_permission for our agent ---
info "GET /v1/agents and check one agent has object_permission..."
LIST_RESP=$(curl -s -w "\n%{http_code}" -X GET "$BASE_URL/v1/agents" -H "$AUTH_HEADER")
LIST_CODE=$(echo "$LIST_RESP" | tail -n1)
LIST_BODY=$(echo "$LIST_RESP" | sed '$d')
if [[ "$LIST_CODE" != "200" ]]; then
fail "GET /v1/agents returned $LIST_CODE"
fi
AGENT_IN_LIST=$(echo "$LIST_BODY" | jq --arg id "$AGENT_ID" '.[] | select(.agent_id == $id)')
if [[ -z "$AGENT_IN_LIST" ]]; then
fail "GET /v1/agents did not return agent $AGENT_ID (list might be key-scoped)"
fi
OP_LIST=$(echo "$AGENT_IN_LIST" | jq '.object_permission')
if [[ "$OP_LIST" == "null" || -z "$OP_LIST" ]]; then
fail "GET /v1/agents list entry for agent missing object_permission"
fi
pass "GET /v1/agents list includes object_permission for agent"
# --- 3. GET /v1/agents/{agent_id} returns object_permission ---
info "GET /v1/agents/{agent_id}..."
GET_RESP=$(curl -s -w "\n%{http_code}" -X GET "$BASE_URL/v1/agents/$AGENT_ID" -H "$AUTH_HEADER")
GET_CODE=$(echo "$GET_RESP" | tail -n1)
GET_BODY=$(echo "$GET_RESP" | sed '$d')
if [[ "$GET_CODE" != "200" ]]; then
fail "GET /v1/agents/$AGENT_ID returned $GET_CODE. Body: $GET_BODY"
fi
OP_GET=$(echo "$GET_BODY" | jq '.object_permission')
if [[ "$OP_GET" == "null" || -z "$OP_GET" ]]; then
fail "GET /v1/agents/$AGENT_ID response missing object_permission"
fi
TOOL_PERMS=$(echo "$OP_GET" | jq -r '.mcp_tool_permissions.server_1 | join(",")')
if [[ "$TOOL_PERMS" != "tool_a,tool_b" ]]; then
fail "object_permission.mcp_tool_permissions.server_1 unexpected: $TOOL_PERMS"
fi
pass "GET /v1/agents/{agent_id} returns object_permission with mcp_tool_permissions"
# --- 4. PATCH /v1/agents/{agent_id} with new object_permission ---
info "PATCH /v1/agents/{agent_id} with updated object_permission..."
PATCH_RESP=$(curl -s -w "\n%{http_code}" -X PATCH "$BASE_URL/v1/agents/$AGENT_ID" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{
"object_permission": {
"mcp_servers": ["server_3"],
"mcp_tool_permissions": {"server_3": ["tool_x"]}
}
}')
PATCH_CODE=$(echo "$PATCH_RESP" | tail -n1)
PATCH_BODY=$(echo "$PATCH_RESP" | sed '$d')
if [[ "$PATCH_CODE" != "200" ]]; then
fail "PATCH /v1/agents/$AGENT_ID returned $PATCH_CODE. Body: $PATCH_BODY"
fi
OP_PATCH=$(echo "$PATCH_BODY" | jq '.object_permission')
if [[ "$OP_PATCH" == "null" || -z "$OP_PATCH" ]]; then
fail "PATCH response missing object_permission"
fi
PATCH_SERVERS=$(echo "$OP_PATCH" | jq -r '.mcp_servers | join(",")')
if [[ "$PATCH_SERVERS" != "server_3" ]]; then
fail "PATCH object_permission.mcp_servers unexpected: $PATCH_SERVERS"
fi
pass "PATCH /v1/agents/{agent_id} updates and returns object_permission"
# --- 5. Create agent without object_permission; GET should still work ---
info "Creating agent without object_permission..."
AGENT_NAME_2="test-agent-no-mcp-$(date +%s)"
CREATE2_RESP=$(curl -s -w "\n%{http_code}" -X POST "$BASE_URL/v1/agents" \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d '{
"agent_name": "'"$AGENT_NAME_2"'",
"agent_card_params": {
"protocolVersion": "1.0",
"name": "No MCP Agent",
"description": "No object_permission",
"url": "http://localhost:9999/",
"version": "1.0.0",
"defaultInputModes": ["text"],
"defaultOutputModes": ["text"],
"capabilities": {},
"skills": []
}
}')
CODE2=$(echo "$CREATE2_RESP" | tail -n1)
BODY2=$(echo "$CREATE2_RESP" | sed '$d')
if [[ "$CODE2" != "200" ]]; then
fail "POST /v1/agents (no object_permission) returned $CODE2. Body: $BODY2"
fi
AGENT_ID_2=$(echo "$BODY2" | jq -r '.agent_id')
# object_permission may be null or absent
pass "Created agent without object_permission: $AGENT_ID_2"
# --- 6. Cleanup: delete both agents ---
info "Deleting test agents..."
for AID in "$AGENT_ID" "$AGENT_ID_2"; do
DEL_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE "$BASE_URL/v1/agents/$AID" -H "$AUTH_HEADER")
if [[ "$DEL_CODE" != "200" ]]; then
info "DELETE /v1/agents/$AID returned $DEL_CODE (non-fatal)"
fi
done
pass "Cleanup done"
echo ""
echo -e "${GREEN}All endpoint checks passed.${NC}"