fix(mcp): upgrade to MCP SDK 2.3.0 and raise the per-event SSE size cap (#47034)

* fix(mcp): upgrade to MCP SDK 2.3.0 and raise the per-event SSE size cap

MCP SDK 2.x reads Streamable HTTP responses through httpx2's SSE parser,
which rejects any single event over 1 MiB, so a large tools/call result
failed with 'SSE stream ended without a response'. mcp 2.3.0 exposes
max_sse_event_size, and MCPClient now passes a 64 MiB default,
configurable with LITELLM_MCP_MAX_SSE_EVENT_SIZE_BYTES

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(mcp): fall back for invalid SSE event limits

* test(mcp): verify configured SSE limits and recovery

* chore(mcp): remove redundant dependency comment

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joshua Valluru <326636767+joshua-berri@users.noreply.github.com>
This commit is contained in:
joshua-berri 2026-10-20 16:00:00 -07:00 • committed by GitHub
parent 9fbeebc988
commit 4aec80f5ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 131 additions and 20 deletions

View file

@ -258,6 +258,9 @@ MCP_PER_USER_TOKEN_EXPIRY_BUFFER_SECONDS: Final = get_env_int("MCP_PER_USER_TOKE
# MCP timeout defaults (seconds). Override via env vars for slow/custom MCP servers. # MCP timeout defaults (seconds). Override via env vars for slow/custom MCP servers.
MCP_CLIENT_TIMEOUT: Final = get_env_float("LITELLM_MCP_CLIENT_TIMEOUT", 60.0) MCP_CLIENT_TIMEOUT: Final = get_env_float("LITELLM_MCP_CLIENT_TIMEOUT", 60.0)
MCP_MAX_SSE_EVENT_SIZE_BYTES: Final = get_env_int_in_range(
"LITELLM_MCP_MAX_SSE_EVENT_SIZE_BYTES", 64 * 1024 * 1024, 1, sys.maxsize
)
MCP_TOOL_LISTING_TIMEOUT: Final = get_env_float("LITELLM_MCP_TOOL_LISTING_TIMEOUT", 30.0) MCP_TOOL_LISTING_TIMEOUT: Final = get_env_float("LITELLM_MCP_TOOL_LISTING_TIMEOUT", 30.0)
MCP_UPSTREAM_RATE_LIMIT_MAX_RETRIES: Final = get_env_int("LITELLM_MCP_UPSTREAM_RATE_LIMIT_MAX_RETRIES", 3) MCP_UPSTREAM_RATE_LIMIT_MAX_RETRIES: Final = get_env_int("LITELLM_MCP_UPSTREAM_RATE_LIMIT_MAX_RETRIES", 3)
MCP_UPSTREAM_RATE_LIMIT_BASE_DELAY_SECONDS: Final = get_env_float( MCP_UPSTREAM_RATE_LIMIT_BASE_DELAY_SECONDS: Final = get_env_float(

View file

@ -95,6 +95,7 @@ from pydantic import AnyUrl, TypeAdapter
from litellm._logging import verbose_logger from litellm._logging import verbose_logger
from litellm.constants import ( from litellm.constants import (
MCP_CLIENT_TIMEOUT, MCP_CLIENT_TIMEOUT,
MCP_MAX_SSE_EVENT_SIZE_BYTES,
MCP_NPM_CACHE_DIR, MCP_NPM_CACHE_DIR,
MCP_TOOL_LISTING_MAX_PAGES, MCP_TOOL_LISTING_MAX_PAGES,
MCP_TOOL_LISTING_TIMEOUT, MCP_TOOL_LISTING_TIMEOUT,
@ -647,6 +648,7 @@ class MCPClient:
transport_ctx: Final = streamable_http_client( transport_ctx: Final = streamable_http_client(
url=self.server_url, url=self.server_url,
http_client=http_client, http_client=http_client,
max_sse_event_size=MCP_MAX_SSE_EVENT_SIZE_BYTES,
) )
return transport_ctx, http_client return transport_ctx, http_client

View file

@ -75,8 +75,8 @@ proxy = [
"boto3>=1.43.1,<2.0", "boto3>=1.43.1,<2.0",
"azure-identity>=1.25.2,<2.0", "azure-identity>=1.25.2,<2.0",
"azure-storage-blob>=12.28.0,<13.0", "azure-storage-blob>=12.28.0,<13.0",
"mcp>=2.2.0,<3", "mcp>=2.3.0,<3",
"httpx2>=2.5.0,<3", "httpx2>=2.10.0,<3",
"pydantic>=2.12.0,<3", "pydantic>=2.12.0,<3",
"litellm-proxy-extras==0.4.111", "litellm-proxy-extras==0.4.111",
"litellm-enterprise==0.1.79", "litellm-enterprise==0.1.79",
@ -125,7 +125,7 @@ utils = [
"numpydoc>=1.8.0,<2.0", "numpydoc>=1.8.0,<2.0",
] ]
caching = ["diskcache>=5.6.3,<6.0"] caching = ["diskcache>=5.6.3,<6.0"]
mcp = ["mcp>=2.2.0,<3", "httpx2>=2.5.0,<3", "pydantic>=2.12.0,<3"] mcp = ["mcp>=2.3.0,<3", "httpx2>=2.10.0,<3", "pydantic>=2.12.0,<3"]
# Driver for the MongoDB Atlas vector store; Atlas Vector Search has no HTTP query API. # Driver for the MongoDB Atlas vector store; Atlas Vector Search has no HTTP query API.
# The floor is 4.9 because that is the release AsyncMongoClient landed in. # The floor is 4.9 because that is the release AsyncMongoClient landed in.
# SAML SSO for the admin UI. python3-saml pulls in xmlsec/lxml, whose wheels # SAML SSO for the admin UI. python3-saml pulls in xmlsec/lxml, whose wheels
@ -243,7 +243,7 @@ e2e-dev = [
"locust==2.45.0", "locust==2.45.0",
"anthropic==0.84.0", "anthropic==0.84.0",
"psutil==7.2.2", "psutil==7.2.2",
"mcp>=2.2.0,<3", "mcp>=2.3.0,<3",
] ]
admin-mcp = [ admin-mcp = [
"litellm-admin-mcp @ https://github.com/BerriAI/liteadmin-mcp/archive/d35ec9c19c117d4c50cc1eccf6ce1296aac25a1a.tar.gz#sha256=86b930f6706fb2da10d53d1798ee7fd14e44fb0afdef0da122cf0e6bb88fa2c3 ; python_version >= '3.12'", "litellm-admin-mcp @ https://github.com/BerriAI/liteadmin-mcp/archive/d35ec9c19c117d4c50cc1eccf6ce1296aac25a1a.tar.gz#sha256=86b930f6706fb2da10d53d1798ee7fd14e44fb0afdef0da122cf0e6bb88fa2c3 ; python_version >= '3.12'",
@ -303,7 +303,7 @@ healthcheck = [
benchmarks = [ benchmarks = [
"pytest==9.0.3", "pytest==9.0.3",
"pytest-codspeed==4.3.0", "pytest-codspeed==4.3.0",
"mcp>=2.2.0,<3", "mcp>=2.3.0,<3",
"a2a-sdk==1.1.0", "a2a-sdk==1.1.0",
] ]
@ -363,6 +363,7 @@ override-dependencies = [
# a2a-sdk 1.x requires packaging>=24.0; lunary 1.4.x still caps at <24.0. # a2a-sdk 1.x requires packaging>=24.0; lunary 1.4.x still caps at <24.0.
"packaging>=24.0", "packaging>=24.0",
"cryptography>=50.0.0,<51.0", "cryptography>=50.0.0,<51.0",
"mcp>=2.3.0,<3",
] ]
default-groups = ["dev"] default-groups = ["dev"]
required-version = ">=0.10.9" required-version = ">=0.10.9"

View file

@ -282,6 +282,7 @@ class ScriptedTool:
respond: Callable[[JsonRpc], Reply | JsonRpc] respond: Callable[[JsonRpc], Reply | JsonRpc]
description: str | Callable[[Mapping[str, str]], str] | None = None description: str | Callable[[Mapping[str, str]], str] | None = None
input_schema: JsonRpc = field(default_factory=lambda: {"type": "object"}) input_schema: JsonRpc = field(default_factory=lambda: {"type": "object"})
event_stream: bool = False
def listing(self, headers: Mapping[str, str]) -> JsonRpc: def listing(self, headers: Mapping[str, str]) -> JsonRpc:
described: Final = self.description(headers) if callable(self.description) else self.description described: Final = self.description(headers) if callable(self.description) else self.description
@ -334,7 +335,10 @@ def scripted_peer(*tools: ScriptedTool) -> Iterator[McpPeer]:
if tool is None: if tool is None:
return jsonrpc_error(identity, -32602, "unknown tool") return jsonrpc_error(identity, -32602, "unknown tool")
produced: Final = tool.respond(body["params"]) produced: Final = tool.respond(body["params"])
return produced if isinstance(produced, Reply) else jsonrpc_reply(identity, produced) reply: Final = produced if isinstance(produced, Reply) else jsonrpc_reply(identity, produced)
if not tool.event_stream:
return reply
return Reply(content_type="text/event-stream", body=b"event: message\ndata: " + reply.body + b"\n\n")
with wire_server(provider) as wire: with wire_server(provider) as wire:
yield McpPeer(wire.url + "/mcp", observed) yield McpPeer(wire.url + "/mcp", observed)

View file

@ -2,10 +2,12 @@ import builtins
import json import json
import uuid import uuid
from collections.abc import Mapping from collections.abc import Mapping
from pathlib import Path
from typing import Final from typing import Final
import httpx import httpx
import pytest import pytest
import yaml
from mcp.shared.exceptions import MCPError from mcp.shared.exceptions import MCPError
from integration._support.client import Gateway from integration._support.client import Gateway
from integration._support.database import read_rows from integration._support.database import read_rows
@ -17,15 +19,21 @@ from integration._support.mcp import (
McpCaller, McpCaller,
Outcome, Outcome,
PeerKind, PeerKind,
ScriptedTool,
_outcome_from_rpc, _outcome_from_rpc,
_parse_rpc_body, _parse_rpc_body,
call_tool,
listed_tools,
mcp_peer, mcp_peer,
official_client_outcomes, official_client_outcomes,
peer_of, peer_of,
register_mcp, register_mcp,
scripted_peer,
text_result,
tool_calls, tool_calls,
) )
from integration._support.mcp_grants import create_toolset from integration._support.mcp_grants import create_toolset
from integration._support.process import owned_proxy
ADD: Final = {"http": "add", "sse": "add", "stdio": "add", "openapi": "getpet"} ADD: Final = {"http": "add", "sse": "add", "stdio": "add", "openapi": "getpet"}
ARGUMENTS: Final = {"add": {"a": 3, "b": 4}, "getpet": {"petId": "7"}} ARGUMENTS: Final = {"add": {"a": 3, "b": 4}, "getpet": {"petId": "7"}}
@ -817,3 +825,67 @@ def test_sdk_progress_and_cancellation_reach_upstream_without_losing_session(
), ),
) as gateway: ) as gateway:
asyncio.run(exercise(gateway)) asyncio.run(exercise(gateway))
def test_tool_result_larger_than_one_mib_in_a_single_sse_event_reaches_the_caller(gateway: Gateway) -> None:
payload: Final = "x" * (3 * 1024 * 1024)
tool: Final = ScriptedTool("dump", lambda _: text_result(payload), event_stream=True)
with scripted_peer(tool) as peer, gateway.scenario() as scenario:
alias: Final = "bigsse" + uuid.uuid4().hex[:8]
identity: Final = register_mcp(scenario, peer, alias)
key: Final = scenario.key(object_permission={"mcp_servers": [identity]})
name: Final = next(full for full in listed_tools(gateway, key, identity) if full.endswith("dump"))
response: Final = call_tool(gateway, key, identity, name, {})
assert response.status_code == 200, response.text[:500]
body: Final = response.json()
assert body["isError"] is False, response.text[:500]
assert body["content"][0]["text"] == payload
def test_configured_sse_limit_rejects_large_results_and_allows_the_next_call(tmp_path: Path) -> None:
limit: Final = 4096
payload: Final = "x" * 512
small: Final = ScriptedTool("small", lambda _: text_result(payload), event_stream=True)
large: Final = ScriptedTool("large", lambda _: text_result("x" * (2 * limit)), event_stream=True)
key: Final = "sk-local-mcp-sse-limit-regression-key"
with scripted_peer(small, large) as peer, httpx.Client() as client:
config: Final = tmp_path / "sse-limit.yaml"
config.write_text(
yaml.safe_dump(
{
"general_settings": {"master_key": key, "store_model_in_db": False},
"mcp_servers": {"limited": peer.registration()},
}
)
)
seed: Final = Gateway(client, key, peer.url)
with owned_proxy(
seed,
tmp_path / "proxy",
{
"STORE_MODEL_IN_DB": "False",
"DISABLE_SCHEMA_UPDATE": "true",
"LITELLM_LOCAL_MODEL_COST_MAP": "True",
"LITELLM_MCP_MAX_SSE_EVENT_SIZE_BYTES": str(limit),
},
config=config,
database_setup=(),
remove_environment=(
"DATABASE_URL",
"DATABASE_URL_READ_REPLICA",
"DIRECT_URL",
"INTEGRATION_PROXY_DATABASE_URL",
"INTEGRATION_PROXY_READ_REPLICA_URL",
),
) as gateway:
assert set(listed_tools(gateway, key)) == {"small", "large"}
for name in ("small", "large", "small"):
response: Final = call_tool(gateway, key, "limited", name, {})
assert response.status_code == 200, response.text[:500]
body: Final = response.json()
assert body["isError"] is (name == "large"), response.text[:500]
if name == "large":
assert str(limit) in body["content"][0]["text"], response.text[:500]
else:
assert body["content"][0]["text"] == payload, response.text[:500]

View file

@ -1500,8 +1500,8 @@ def test_mcp_extra_matches_proxy_extra_and_supports_streamable_http():
} }
specifier: Final = Requirement(mcp_extra["mcp"]).specifier specifier: Final = Requirement(mcp_extra["mcp"]).specifier
assert not specifier.contains("1.28.1") assert not specifier.contains("2.2.0")
assert specifier.contains("2.2.0") assert specifier.contains("2.3.0")
with (pyproject_path.parent / "uv.lock").open("rb") as f: with (pyproject_path.parent / "uv.lock").open("rb") as f:
locked = tomllib.load(f) locked = tomllib.load(f)
mcp_versions: Final = [package["version"] for package in locked["package"] if package["name"] == "mcp"] mcp_versions: Final = [package["version"] for package in locked["package"] if package["name"] == "mcp"]

View file

@ -1,6 +1,7 @@
import ast, asyncio, os import ast, asyncio, os
import inspect import inspect
import json import json
import sys
from unittest import mock from unittest import mock
import httpx import httpx
@ -117,6 +118,33 @@ def test_cli_jwt_expiration_hours_from_environment(
importlib.reload(litellm.constants) importlib.reload(litellm.constants)
@pytest.mark.parametrize(
("raw", "expected"),
(
(None, 64 * 1024 * 1024),
("", 64 * 1024 * 1024),
("invalid", 64 * 1024 * 1024),
("0", 64 * 1024 * 1024),
("-1", 64 * 1024 * 1024),
("1", 1),
("200000", 200000),
(str(sys.maxsize), sys.maxsize),
(str(sys.maxsize + 1), 64 * 1024 * 1024),
),
)
def test_mcp_sse_event_limit_uses_positive_override_or_default(raw: str | None, expected: int) -> None:
try:
with pytest.MonkeyPatch.context() as environment:
if raw is None:
environment.delenv("LITELLM_MCP_MAX_SSE_EVENT_SIZE_BYTES", raising=False)
else:
environment.setenv("LITELLM_MCP_MAX_SSE_EVENT_SIZE_BYTES", raw)
importlib.reload(constants)
assert constants.MCP_MAX_SSE_EVENT_SIZE_BYTES == expected
finally:
importlib.reload(constants)
def _is_blank(value: object) -> bool: def _is_blank(value: object) -> bool:
return isinstance(value, str) and not value.strip() return isinstance(value, str) and not value.strip()

25
uv.lock generated
View file

@ -29,6 +29,7 @@ constraints = [
] ]
overrides = [ overrides = [
{ name = "cryptography", specifier = ">=50.0.0,<51.0" }, { name = "cryptography", specifier = ">=50.0.0,<51.0" },
{ name = "mcp", specifier = ">=2.3.0,<3" },
{ name = "packaging", specifier = ">=24.0" }, { name = "packaging", specifier = ">=24.0" },
] ]
@ -4902,8 +4903,8 @@ requires-dist = [
{ name = "gunicorn", marker = "extra == 'proxy'", specifier = ">=23.0.0,<24.0" }, { name = "gunicorn", marker = "extra == 'proxy'", specifier = ">=23.0.0,<24.0" },
{ name = "hiredis", marker = "extra == 'proxy'", specifier = ">=3.0.0,<4.0" }, { name = "hiredis", marker = "extra == 'proxy'", specifier = ">=3.0.0,<4.0" },
{ name = "httpx", extras = ["http2"], specifier = ">=0.28.0,<1.0" }, { name = "httpx", extras = ["http2"], specifier = ">=0.28.0,<1.0" },
{ name = "httpx2", marker = "extra == 'mcp'", specifier = ">=2.5.0,<3" }, { name = "httpx2", marker = "extra == 'mcp'", specifier = ">=2.10.0,<3" },
{ name = "httpx2", marker = "extra == 'proxy'", specifier = ">=2.5.0,<3" }, { name = "httpx2", marker = "extra == 'proxy'", specifier = ">=2.10.0,<3" },
{ name = "huggingface-hub", specifier = ">=0.34.0,<2.0" }, { name = "huggingface-hub", specifier = ">=0.34.0,<2.0" },
{ name = "importlib-metadata", specifier = ">=8.0.0,<9.0" }, { name = "importlib-metadata", specifier = ">=8.0.0,<9.0" },
{ name = "inquirerpy", marker = "extra == 'cli'", specifier = ">=0.3.4,<1.0" }, { name = "inquirerpy", marker = "extra == 'cli'", specifier = ">=0.3.4,<1.0" },
@ -4916,8 +4917,8 @@ requires-dist = [
{ name = "litellm-proxy-extras", marker = "extra == 'proxy'", editable = "litellm-proxy-extras" }, { name = "litellm-proxy-extras", marker = "extra == 'proxy'", editable = "litellm-proxy-extras" },
{ name = "llm-sandbox", marker = "extra == 'proxy-runtime'", specifier = ">=0.3.39,<1.0" }, { name = "llm-sandbox", marker = "extra == 'proxy-runtime'", specifier = ">=0.3.39,<1.0" },
{ name = "mangum", marker = "extra == 'proxy-runtime'", specifier = ">=0.17.0,<1.0" }, { name = "mangum", marker = "extra == 'proxy-runtime'", specifier = ">=0.17.0,<1.0" },
{ name = "mcp", marker = "extra == 'mcp'", specifier = ">=2.2.0,<3" }, { name = "mcp", marker = "extra == 'mcp'", specifier = ">=2.3.0,<3" },
{ name = "mcp", marker = "extra == 'proxy'", specifier = ">=2.2.0,<3" }, { name = "mcp", marker = "extra == 'proxy'", specifier = ">=2.3.0,<3" },
{ name = "mlflow", marker = "extra == 'mlflow'", specifier = ">=3.11.1,<4.0" }, { name = "mlflow", marker = "extra == 'mlflow'", specifier = ">=3.11.1,<4.0" },
{ name = "numpy", marker = "extra == 'stt-nvidia-riva'", specifier = ">=1.26.0" }, { name = "numpy", marker = "extra == 'stt-nvidia-riva'", specifier = ">=1.26.0" },
{ name = "numpydoc", marker = "extra == 'utils'", specifier = ">=1.8.0,<2.0" }, { name = "numpydoc", marker = "extra == 'utils'", specifier = ">=1.8.0,<2.0" },
@ -4977,7 +4978,7 @@ provides-extras = ["proxy", "cli", "extra-proxy", "utils", "caching", "mcp", "sa
admin-mcp = [{ name = "litellm-admin-mcp", marker = "python_full_version >= '3.12'", url = "https://github.com/BerriAI/liteadmin-mcp/archive/d35ec9c19c117d4c50cc1eccf6ce1296aac25a1a.tar.gz" }] admin-mcp = [{ name = "litellm-admin-mcp", marker = "python_full_version >= '3.12'", url = "https://github.com/BerriAI/liteadmin-mcp/archive/d35ec9c19c117d4c50cc1eccf6ce1296aac25a1a.tar.gz" }]
benchmarks = [ benchmarks = [
{ name = "a2a-sdk", specifier = "==1.1.0" }, { name = "a2a-sdk", specifier = "==1.1.0" },
{ name = "mcp", specifier = ">=2.2.0,<3" }, { name = "mcp", specifier = ">=2.3.0,<3" },
{ name = "pytest", specifier = "==9.0.3" }, { name = "pytest", specifier = "==9.0.3" },
{ name = "pytest-codspeed", specifier = "==4.3.0" }, { name = "pytest-codspeed", specifier = "==4.3.0" },
] ]
@ -5055,7 +5056,7 @@ dev = [
e2e-dev = [ e2e-dev = [
{ name = "anthropic", specifier = "==0.84.0" }, { name = "anthropic", specifier = "==0.84.0" },
{ name = "locust", specifier = "==2.45.0" }, { name = "locust", specifier = "==2.45.0" },
{ name = "mcp", specifier = ">=2.2.0,<3" }, { name = "mcp", specifier = ">=2.3.0,<3" },
{ name = "playwright", specifier = "==1.61.0" }, { name = "playwright", specifier = "==1.61.0" },
{ name = "psutil", specifier = "==7.2.2" }, { name = "psutil", specifier = "==7.2.2" },
{ name = "websockets", specifier = ">=15.0.1,<16.0" }, { name = "websockets", specifier = ">=15.0.1,<16.0" },
@ -5570,7 +5571,7 @@ wheels = [
[[package]] [[package]]
name = "mcp" name = "mcp"
version = "2.2.0" version = "2.3.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "anyio" }, { name = "anyio" },
@ -5588,22 +5589,22 @@ dependencies = [
{ name = "typing-inspection" }, { name = "typing-inspection" },
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/76/31/ac54fb0fdd5b37de704486e288bba4fbbb463f24cfcfedbede407b854513/mcp-2.2.0.tar.gz", hash = "sha256:2dc37ecb1974becdcebdbf7561e7c15a07dbbf20ba21ba16c3593b3038b3afbd", size = 4084129, upload-time = "2026-09-07T16:06:23.439Z" } sdist = { url = "https://files.pythonhosted.org/packages/9d/8d/e0d339616f4810e9051d4aba6887afab289ab1f81875fe908b606cdfd0e3/mcp-2.3.0.tar.gz", hash = "sha256:8b147a50441cf059dc88c684e0aeed3687f0aa0f39c6cde7b90330effd2b34d8", size = 4171955, upload-time = "2026-10-02T22:06:56.091Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/1b/ff/8e7eade68b8a28f7da0ed1085544341b51f9c935dbf6b95c76b7edfea6a0/mcp-2.2.0-py3-none-any.whl", hash = "sha256:bde982589473a060ae145e3406e9a5333fe538c97229ba841f5a7f92be004f81", size = 365656, upload-time = "2026-09-07T16:06:19.711Z" }, { url = "https://files.pythonhosted.org/packages/20/d7/4a2878f128df729db3ca04e71bfce26405cc2e0c9ebe19c592feeb4c61b9/mcp-2.3.0-py3-none-any.whl", hash = "sha256:dd0c44c089d16453e8ae31a3877a0054d7a2314caaa81f5e0541b9b1734b2377", size = 368664, upload-time = "2026-10-02T22:06:52.119Z" },
] ]
[[package]] [[package]]
name = "mcp-types" name = "mcp-types"
version = "2.2.0" version = "2.3.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "pydantic" }, { name = "pydantic" },
{ name = "typing-extensions" }, { name = "typing-extensions" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/ae/91/762d7755d971aff8a28d75f7961656148edf27875c8026e6385aaab08ae7/mcp_types-2.2.0.tar.gz", hash = "sha256:d3ed53703ddd10d9c6399f29d322bb66f3f67ab41348ac8556ba23e07fedefad", size = 65892, upload-time = "2026-09-07T16:06:25.187Z" } sdist = { url = "https://files.pythonhosted.org/packages/9e/2d/7c251e34207f6c51000312fc8839111ac45cfe02023f90b44e7f1051dd8e/mcp_types-2.3.0.tar.gz", hash = "sha256:d1e46549edb35ee19a94940fcee6d1addd7e589ab7ea92dda83f5d84781fc362", size = 65898, upload-time = "2026-10-02T22:06:57.863Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/8f/d7/6ffba5d8cd5dd9b8a19478875c50e04945314ba5074e84d749283f27f62d/mcp_types-2.2.0-py3-none-any.whl", hash = "sha256:ea476b73ee86709ab5abc9452385ed36cc05907e582355622e294595c9a04f13", size = 69106, upload-time = "2026-09-07T16:06:21.461Z" }, { url = "https://files.pythonhosted.org/packages/ce/48/32583066b155bb5d4507e7887dc88ee23118cc4de8e2a12870c49719e2a8/mcp_types-2.3.0-py3-none-any.whl", hash = "sha256:968efdbdaedfab06adae40d378a34395f1090c5921d4be3c9cde283aaf76d91d", size = 69108, upload-time = "2026-10-02T22:06:54.099Z" },
] ]
[[package]] [[package]]