added support for metadata

This commit is contained in:
shivam 2026-03-20 18:45:37 -07:00
parent f0efa5777d
commit ba20699312
6 changed files with 96 additions and 48 deletions

View file

@ -33,24 +33,17 @@ commands:
- run:
name: Install Dependencies
command: |
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
pip install "pytest-mock==3.12.0"
pip install "pytest==7.3.1"
pip install "pytest-retry==1.6.3"
pip install "pytest-cov==5.0.0"
pip install "pytest-asyncio==0.21.1"
pip install "respx==0.22.0"
pip install "hypercorn==0.17.3"
pip install "pydantic==2.11.0"
pip install "mcp==1.25.0"
pip install "requests-mock>=1.12.1"
pip install "responses==0.25.7"
pip install "pytest-xdist==3.6.1"
pip install "pytest-timeout==2.2.0"
pip install "semantic_router==0.1.10"
pip install "fastapi-offline==1.7.3"
pip install "a2a"
python -m pip install --upgrade pip uv
# Use uv for the heavy requirements.txt (10-100x faster than pip)
uv pip install --system -r requirements.txt
# Use pip for test deps (small set, avoids uv strict-resolution
# conflicts with transitive dep pins like openai<2 and pydantic>=2.11.5)
pip install "pytest-mock==3.12.0" "pytest==7.3.1" "pytest-retry==1.6.3" \
"pytest-asyncio==0.21.1" "respx==0.22.0" "hypercorn==0.17.3" \
"pydantic==2.11.0" "mcp==1.26.0" "requests-mock>=1.12.1" \
"responses==0.25.7" "pytest-xdist==3.6.1" "pytest-timeout==2.2.0" \
"pytest-cov==5.0.0" "semantic_router==0.1.10" "fastapi-offline==1.7.3" \
"a2a" "parameterized>=0.9.0"
- setup_litellm_enterprise_pip
- save_cache:
paths:
@ -1367,7 +1360,8 @@ jobs:
pip install "pytest-asyncio==0.21.1"
pip install "respx==0.22.0"
pip install "pydantic==2.11.0"
pip install "mcp==1.25.0"
pip install "mcp==1.26.0"
pip install "pytest-xdist==3.6.1"
# Run pytest and generate JUnit XML report
- run:
name: Run tests
@ -1874,7 +1868,7 @@ jobs:
pip install "respx==0.22.0"
pip install "hypercorn==0.17.3"
pip install "pydantic==2.11.0"
pip install "mcp==1.25.0"
pip install "mcp==1.26.0"
pip install "requests-mock>=1.12.1"
pip install "responses==0.25.7"
pip install "pytest-xdist==3.6.1"
@ -2234,7 +2228,7 @@ jobs:
pip install "pytest-asyncio==0.21.1"
pip install "pytest-cov==5.0.0"
pip install "tomli==2.2.1"
pip install "mcp==1.25.0"
pip install "mcp==1.26.0"
- run:
name: Run tests
command: |

View file

@ -13,7 +13,7 @@ google-cloud-aiplatform==1.43.0
google-cloud-iam==2.19.1
fastapi-sso==0.16.0
uvloop==0.21.0
mcp==1.25.0 # for MCP server
mcp==1.26.0 # for MCP server
semantic_router==0.1.10 # for auto-routing with litellm
fastuuid==0.12.0
responses==0.25.7 # for proxy client tests

View file

@ -169,6 +169,32 @@ if MCP_AVAILABLE:
mcp_info: Optional[MCPInfo] = None
model_config = ConfigDict(arbitrary_types_allowed=True)
def _normalize_resource_contents(contents: list) -> List[ReadResourceContents]:
"""Normalize ResourceContents to ReadResourceContents, preserving meta (MCP 1.26.0+)."""
normalized: List[ReadResourceContents] = []
for content in contents:
meta = getattr(content, "meta", None)
if meta is None and hasattr(content, "model_dump"):
d = content.model_dump()
meta = d.get("meta") or d.get("_meta")
if isinstance(content, TextResourceContents):
normalized.append(
ReadResourceContents(
content=content.text,
mime_type=content.mimeType,
meta=meta,
)
)
elif isinstance(content, BlobResourceContents):
normalized.append(
ReadResourceContents(
content=content.blob,
mime_type=None,
meta=meta,
)
)
return normalized
########################################################
############ Initialize the MCP Server #################
########################################################
@ -623,26 +649,7 @@ if MCP_AVAILABLE:
raw_headers=raw_headers,
)
normalized_contents: List[ReadResourceContents] = []
for content in read_resource_result.contents:
if isinstance(content, TextResourceContents):
text_content: TextResourceContents = content
normalized_contents.append(
ReadResourceContents(
content=text_content.text,
mime_type=text_content.mimeType,
)
)
elif isinstance(content, BlobResourceContents):
blob_content: BlobResourceContents = content
normalized_contents.append(
ReadResourceContents(
content=blob_content.blob,
mime_type=None,
)
)
return normalized_contents
return _normalize_resource_contents(read_resource_result.contents)
########################################################
############ End of MCP Server Routes ##################

View file

@ -59,7 +59,7 @@ pynacl = {version = "^1.5.0", optional = true}
websockets = {version = "^15.0.1", optional = true}
boto3 = { version = "^1.40.76", optional = true }
redisvl = {version = "^0.4.1", optional = true, markers = "python_version >= '3.9' and python_version < '3.14'"}
mcp = {version = ">=1.25.0,<2.0.0", optional = true, python = ">=3.10"}
mcp = {version = ">=1.26.0,<2.0.0", optional = true, python = ">=3.10"}
a2a-sdk = {version = "^0.3.22", optional = true, python = ">=3.10"}
litellm-proxy-extras = {version = "^0.4.53", optional = true}
rich = {version = "^13.7.1", optional = true}

View file

@ -30,7 +30,7 @@ google-cloud-aiplatform==1.133.0 # for vertex ai calls
google-cloud-iam==2.19.1 # for GCP IAM Redis authentication
google-genai==1.37.0
anthropic[vertex]==0.54.0
mcp==1.25.0 ; python_version >= "3.10" # for MCP server
mcp==1.26.0 ; python_version >= "3.10" # for MCP server
# google-generativeai removed - deprecated, replaced by google-genai (line 21)
async_generator==1.10.0 # for async ollama calls
langfuse==2.59.7 # for langfuse self-hosted logging

View file

@ -1,6 +1,5 @@
import asyncio
from datetime import datetime, timedelta
from typing import Optional
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
@ -413,6 +412,57 @@ async def test_mcp_read_resource_success():
assert result is read_result
def test_normalize_resource_contents_passes_metadata():
"""Test that _normalize_resource_contents preserves meta from ResourceContents (MCP 1.26.0+)."""
try:
from litellm.proxy._experimental.mcp_server.server import (
_normalize_resource_contents,
)
except ImportError:
pytest.skip("MCP server not available")
meta = {"version": "1.0", "source": "test"}
contents = [
TextResourceContents(
uri="https://example.com/resource",
text="hello world",
mimeType="text/plain",
meta=meta,
)
]
result = _normalize_resource_contents(contents)
assert len(result) == 1
assert result[0].content == "hello world"
assert result[0].mime_type == "text/plain"
assert result[0].meta == meta
def test_normalize_resource_contents_without_metadata():
"""Test that _normalize_resource_contents works when meta is absent (backward compat)."""
try:
from litellm.proxy._experimental.mcp_server.server import (
_normalize_resource_contents,
)
except ImportError:
pytest.skip("MCP server not available")
contents = [
TextResourceContents(
uri="https://example.com/resource",
text="hello",
mimeType="text/plain",
)
]
result = _normalize_resource_contents(contents)
assert len(result) == 1
assert result[0].content == "hello"
assert result[0].meta is None
@pytest.mark.asyncio
async def test_mcp_read_resource_multiple_servers_error():
try:
@ -707,8 +757,6 @@ async def test_concurrent_initialize_session_managers():
"""Test that concurrent calls to initialize_session_managers don't cause race conditions."""
try:
from litellm.proxy._experimental.mcp_server.server import (
_INITIALIZATION_LOCK,
_SESSION_MANAGERS_INITIALIZED,
initialize_session_managers,
)
except ImportError:
@ -1426,7 +1474,6 @@ async def test_list_tools_with_team_tool_permissions_inheritance():
)
from litellm.proxy._types import (
LiteLLM_ObjectPermissionTable,
LiteLLM_TeamTable,
UserAPIKeyAuth,
)
except ImportError: