mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-24 00:52:24 +00:00
180 lines
7.2 KiB
YAML
180 lines
7.2 KiB
YAML
name: MCP OAuth happy path
|
|
|
|
on:
|
|
pull_request:
|
|
paths:
|
|
- '.github/workflows/test-mcp-oauth-e2e.yml'
|
|
- '.github/e2e-stack/**'
|
|
- 'tests/e2e/*.py'
|
|
- 'tests/e2e/pytest.ini'
|
|
- 'tests/e2e/idp_realm.json'
|
|
- 'tests/e2e/mcp/**'
|
|
- 'litellm/experimental_mcp_client/**'
|
|
- 'litellm/proxy/_experimental/mcp_server/**'
|
|
- 'litellm/proxy/auth/**'
|
|
- 'litellm/proxy/management_endpoints/mcp_management_endpoints.py'
|
|
- 'litellm/proxy/_types.py'
|
|
- 'litellm/types/mcp_server/mcp_server_manager.py'
|
|
- 'litellm/proxy/management_endpoints/*sso*.py'
|
|
- 'litellm/proxy/management_endpoints/sso/**'
|
|
- 'litellm/proxy/common_utils/encrypt_decrypt_utils.py'
|
|
- 'litellm/proxy/proxy_server.py'
|
|
- 'litellm/proxy/schema.prisma'
|
|
- 'ui/litellm-dashboard/src/app/connect/**'
|
|
- 'ui/litellm-dashboard/src/app/mcp/oauth/**'
|
|
- 'pyproject.toml'
|
|
- 'uv.lock'
|
|
workflow_dispatch:
|
|
|
|
permissions: {}
|
|
|
|
concurrency:
|
|
group: mcp-oauth-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
oauth:
|
|
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository
|
|
runs-on: ubuntu-latest
|
|
environment: e2e-changed
|
|
timeout-minutes: 45
|
|
permissions:
|
|
contents: read
|
|
id-token: write
|
|
services:
|
|
postgres:
|
|
image: postgres:16.6
|
|
env:
|
|
POSTGRES_USER: litellm
|
|
POSTGRES_PASSWORD: dbpassword9090
|
|
POSTGRES_DB: litellm
|
|
ports:
|
|
- 5432:5432
|
|
options: >-
|
|
--health-cmd "pg_isready -U litellm"
|
|
--health-interval 5s
|
|
--health-timeout 5s
|
|
--health-retries 10
|
|
env:
|
|
DATABASE_HOST: 127.0.0.1
|
|
DATABASE_PORT: '5432'
|
|
DATABASE_USER: litellm
|
|
DATABASE_PASSWORD: dbpassword9090
|
|
DATABASE_NAME: litellm
|
|
DATABASE_URL: postgresql://litellm:dbpassword9090@127.0.0.1:5432/litellm
|
|
E2E_KEYCLOAK_URL: http://127.0.0.1:8081
|
|
E2E_KEYCLOAK_ADMIN_USER: admin
|
|
E2E_KEYCLOAK_ADMIN_PASSWORD: e2e-ephemeral-idp-not-a-secret
|
|
E2E_FIXTURE_MODE: live
|
|
E2E_PROVIDER_CACHE: '0'
|
|
E2E_MCP_OAUTH_LIVE: '1'
|
|
E2E_REQUIRED_TEST_COUNT: '4'
|
|
steps:
|
|
- name: Checkout the tested source
|
|
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
|
with:
|
|
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
|
persist-credentials: false
|
|
|
|
- name: Require and materialize the upstream login
|
|
env:
|
|
STORAGE_STATE: ${{ secrets.E2E_LINEAR_STORAGE_STATE_B64 }}
|
|
run: |
|
|
umask 077
|
|
python3 - <<'PY'
|
|
import base64
|
|
import json
|
|
import os
|
|
import secrets
|
|
from pathlib import Path
|
|
encoded = os.environ.get("STORAGE_STATE", "")
|
|
if not encoded:
|
|
raise SystemExit("E2E_LINEAR_STORAGE_STATE_B64 is required; capture and provision a test-account login")
|
|
state = json.loads(base64.b64decode(encoded, validate=True))
|
|
if not isinstance(state, dict) or not state.get("cookies"):
|
|
raise SystemExit("The captured login must contain browser cookies")
|
|
directory = Path(os.environ["RUNNER_TEMP"]) / "mcp-oauth-private"
|
|
directory.mkdir(mode=0o700)
|
|
path = directory / "linear-state.json"
|
|
path.write_text(json.dumps(state))
|
|
with open(os.environ["GITHUB_ENV"], "a") as output:
|
|
output.write(f"E2E_LINEAR_STORAGE_STATE={path}\n")
|
|
for name in ("LITELLM_MASTER_KEY", "LITELLM_SALT_KEY"):
|
|
value = "sk-e2e-" + secrets.token_hex(24)
|
|
print(f"::add-mask::{value}")
|
|
output.write(f"{name}={value}\n")
|
|
PY
|
|
|
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
with:
|
|
python-version: '3.13'
|
|
- uses: ./.github/actions/setup-uv-with-retries
|
|
with:
|
|
version: '0.10.9'
|
|
- uses: ./.github/actions/cache-cargo-build
|
|
- name: Install the frozen E2E environment
|
|
run: |
|
|
.github/scripts/uv_sync_with_retries.sh --frozen --extra proxy --extra proxy-runtime --extra extra_proxy --group ci --group proxy-dev --group e2e-dev
|
|
uv run --no-sync python scripts/prisma_generate_if_needed.py
|
|
uv run --no-sync playwright install --with-deps chromium
|
|
|
|
- name: Configure license access
|
|
id: aws
|
|
uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6.2.0
|
|
with:
|
|
role-to-assume: ${{ vars.E2E_AWS_ROLE_TO_ASSUME }}
|
|
aws-region: us-east-1
|
|
role-session-name: mcp-oauth-${{ github.run_id }}
|
|
role-duration-seconds: 900
|
|
output-env-credentials: false
|
|
output-credentials: true
|
|
- name: Load the E2E license
|
|
env:
|
|
AWS_ACCESS_KEY_ID: ${{ steps.aws.outputs.aws-access-key-id }}
|
|
AWS_SECRET_ACCESS_KEY: ${{ steps.aws.outputs.aws-secret-access-key }}
|
|
AWS_SESSION_TOKEN: ${{ steps.aws.outputs.aws-session-token }}
|
|
AWS_DEFAULT_REGION: us-east-1
|
|
run: |
|
|
license="$(aws secretsmanager get-secret-value --secret-id litellm-e2e-changed-license --query SecretString --output text)"
|
|
test -n "${license}"
|
|
echo "::add-mask::${license}"
|
|
echo "LITELLM_LICENSE=${license}" >> "${GITHUB_ENV}"
|
|
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
with:
|
|
node-version-file: ui/litellm-dashboard/.nvmrc
|
|
- name: Build the gateway consent UI at the tested commit
|
|
run: |
|
|
cd ui/litellm-dashboard
|
|
../../scripts/with_dashboard_node.sh npm ci
|
|
../../scripts/with_dashboard_node.sh npm run build
|
|
mkdir -p ../../litellm/proxy/_experimental/out
|
|
cp -r out/. ../../litellm/proxy/_experimental/out/
|
|
find ../../litellm/proxy/_experimental/out -name '*.html' ! -name index.html | while read -r page; do
|
|
mkdir -p "${page%.html}"
|
|
mv "${page}" "${page%.html}/index.html"
|
|
done
|
|
|
|
- name: Prepare the isolated database and IdP
|
|
run: |
|
|
umask 077
|
|
bash .github/e2e-stack/start-idp.sh
|
|
uv run --no-sync python migrations/run.py > "${RUNNER_TEMP}/mcp-oauth-private/migrations.log" 2>&1
|
|
|
|
- name: Run every required OAuth variant without retries
|
|
run: |
|
|
umask 077
|
|
uv run --no-sync pytest -c tests/e2e/pytest.ini tests/e2e/mcp/test_mcp_oauth_happy_path_e2e.py \
|
|
--rootdir=. --reruns 0 --tb=short -o junit_family=xunit1 \
|
|
--junitxml="${RUNNER_TEMP}/mcp-oauth-private/results.xml" \
|
|
> "${RUNNER_TEMP}/mcp-oauth-private/pytest.log" 2>&1
|
|
- name: Report JUnit results and reject skipped or missing cases
|
|
if: always()
|
|
run: |
|
|
uv run --no-sync python .github/e2e-stack/assert_tests_ran.py \
|
|
"${RUNNER_TEMP}/mcp-oauth-private/results.xml" tests/e2e/mcp/test_mcp_oauth_happy_path_e2e.py
|
|
- name: Remove private login and logs
|
|
if: always()
|
|
run: |
|
|
docker rm -f e2e-keycloak >/dev/null 2>&1 || true
|
|
rm -rf "${RUNNER_TEMP}/mcp-oauth-private"
|