mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
ci(mcp): add dependency-resolution workflow for the SDK 2 floor
New matrix job across Python 3.10-3.14 verifies uv.lock against the declared floors, installs the locked mcp+proxy extras and runs the MCP unit suites, then resolves the same extras with uv's lowest-direct strategy into a clean venv and runs scripts/check_mcp_sdk_install.py to prove the floor still imports the SDK 2 API surface. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
0d2963fe89
commit
8d8a2c3742
2 changed files with 172 additions and 0 deletions
100
.github/workflows/test-mcp-dependency-resolution.yml
vendored
Normal file
100
.github/workflows/test-mcp-dependency-resolution.yml
vendored
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
name: LiteLLM MCP Dependency Resolution
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- litellm_internal_staging
|
||||
- litellm_oss_staging
|
||||
- "litellm_**"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
resolve:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Detect relevant changes
|
||||
id: changes
|
||||
uses: ./.github/actions/detect-changes
|
||||
|
||||
- name: Set up Python
|
||||
if: steps.changes.outputs.decision != 'skip'
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Set up uv
|
||||
if: steps.changes.outputs.decision != 'skip'
|
||||
uses: ./.github/actions/setup-uv-with-retries
|
||||
with:
|
||||
version: "0.10.9"
|
||||
|
||||
- name: Cache the Rust build
|
||||
if: steps.changes.outputs.decision != 'skip'
|
||||
uses: ./.github/actions/cache-cargo-build
|
||||
|
||||
- name: Verify lockfile
|
||||
if: steps.changes.outputs.decision != 'skip'
|
||||
run: |
|
||||
uv lock --check
|
||||
|
||||
- name: Install locked dependencies
|
||||
if: steps.changes.outputs.decision != 'skip'
|
||||
run: |
|
||||
.github/scripts/uv_sync_with_retries.sh --frozen --python ${{ matrix.python-version }} --group proxy-dev --extra mcp --extra proxy --extra semantic-router
|
||||
|
||||
- name: Check locked MCP SDK installation
|
||||
if: steps.changes.outputs.decision != 'skip'
|
||||
run: |
|
||||
uv run --no-sync python scripts/check_mcp_sdk_install.py
|
||||
|
||||
- name: Cache Prisma binaries
|
||||
if: steps.changes.outputs.decision != 'skip'
|
||||
timeout-minutes: 3
|
||||
uses: ./.github/actions/cache-prisma-binaries
|
||||
|
||||
- name: Generate Prisma client
|
||||
if: steps.changes.outputs.decision != 'skip'
|
||||
timeout-minutes: 3
|
||||
run: |
|
||||
uv run --no-sync prisma generate --schema litellm/proxy/schema.prisma
|
||||
|
||||
- name: Run MCP unit tests
|
||||
if: steps.changes.outputs.decision != 'skip'
|
||||
env:
|
||||
LITELLM_LOCAL_MODEL_COST_MAP: "True"
|
||||
run: |
|
||||
uv run --no-sync pytest -q -p no:cacheprovider -n 4 tests/test_litellm/proxy/_experimental/mcp_server tests/test_litellm/experimental_mcp_client
|
||||
|
||||
- name: Resolve lowest direct dependencies
|
||||
if: steps.changes.outputs.decision != 'skip'
|
||||
run: |
|
||||
uv pip compile pyproject.toml --python-version ${{ matrix.python-version }} --extra mcp --extra proxy --resolution lowest-direct -o lowest-direct.txt
|
||||
|
||||
- name: Install lowest direct dependencies
|
||||
if: steps.changes.outputs.decision != 'skip'
|
||||
run: |
|
||||
uv venv --python ${{ matrix.python-version }} .venv-lowest
|
||||
uv pip install --python .venv-lowest -r lowest-direct.txt -e .
|
||||
|
||||
- name: Check lowest-direct MCP SDK installation
|
||||
if: steps.changes.outputs.decision != 'skip'
|
||||
run: |
|
||||
.venv-lowest/bin/python scripts/check_mcp_sdk_install.py
|
||||
72
scripts/check_mcp_sdk_install.py
Normal file
72
scripts/check_mcp_sdk_install.py
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import importlib
|
||||
import importlib.metadata
|
||||
import sys
|
||||
from typing import Final
|
||||
|
||||
MINIMUM_MCP_VERSION: Final[tuple[int, int, int]] = (2, 2, 0)
|
||||
|
||||
IMPORTED_MODULES: Final[tuple[str, ...]] = (
|
||||
"litellm",
|
||||
"litellm.experimental_mcp_client",
|
||||
"litellm.experimental_mcp_client.client",
|
||||
"litellm.proxy._experimental.mcp_server.server",
|
||||
"litellm.proxy._experimental.mcp_server.mcp_server_manager",
|
||||
"litellm.proxy._experimental.mcp_server.rest_endpoints",
|
||||
)
|
||||
|
||||
|
||||
def _version_tuple(distribution: str) -> tuple[int, ...]:
|
||||
return tuple(int(part) for part in importlib.metadata.version(distribution).split(".") if part.isdigit())
|
||||
|
||||
|
||||
def main() -> int:
|
||||
for module_name in IMPORTED_MODULES:
|
||||
try:
|
||||
importlib.import_module(module_name)
|
||||
except Exception as exc:
|
||||
sys.stderr.write(f"failed to import {module_name}: {exc}\n")
|
||||
return 1
|
||||
|
||||
mcp_version: Final = _version_tuple("mcp")
|
||||
if mcp_version < MINIMUM_MCP_VERSION:
|
||||
sys.stderr.write(f"mcp {importlib.metadata.version('mcp')} below floor 2.2.0\n")
|
||||
return 1
|
||||
|
||||
from mcp_types.version import HANDSHAKE_PROTOCOL_VERSIONS
|
||||
|
||||
for required in ("2024-11-05", "2025-06-18"):
|
||||
if required not in HANDSHAKE_PROTOCOL_VERSIONS:
|
||||
sys.stderr.write(f"HANDSHAKE_PROTOCOL_VERSIONS missing {required}\n")
|
||||
return 1
|
||||
|
||||
scope: Final = {
|
||||
"type": "http",
|
||||
"method": "POST",
|
||||
"path": "/mcp",
|
||||
"headers": [(b"mcp-protocol-version", b"2026-07-28")],
|
||||
}
|
||||
mcp_server: Final = sys.modules["litellm.proxy._experimental.mcp_server.server"]
|
||||
if mcp_server.unsupported_protocol_version(scope) != "2026-07-28":
|
||||
sys.stderr.write("unsupported_protocol_version accepted a modern-only version\n")
|
||||
return 1
|
||||
if (
|
||||
mcp_server.unsupported_protocol_version(dict(scope, headers=[(b"mcp-protocol-version", b"2025-06-18")]))
|
||||
is not None
|
||||
):
|
||||
sys.stderr.write("unsupported_protocol_version rejected a handshake version\n")
|
||||
return 1
|
||||
|
||||
sys.stdout.write(
|
||||
"python {} mcp {} httpx2 {} pydantic {} litellm {}\n".format(
|
||||
sys.version.split()[0],
|
||||
importlib.metadata.version("mcp"),
|
||||
importlib.metadata.version("httpx2"),
|
||||
importlib.metadata.version("pydantic"),
|
||||
importlib.metadata.version("litellm"),
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
Add table
Reference in a new issue