From 3d2d45aa2f36ec1e93142a9f6c615066f856b136 Mon Sep 17 00:00:00 2001 From: Andrey Yoshua Date: Thu, 8 Jan 2026 07:08:23 +0700 Subject: [PATCH 1/2] Deprecating aws_iam_streamablehttp_client --- CHANGELOG.md | 16 ++ mcp_proxy_for_aws/client.py | 82 ++++++++- mcp_proxy_for_aws/utils.py | 1 + pyproject.toml | 4 +- tests/integ/mcp/simple_mcp_client.py | 5 +- tests/unit/test_client.py | 66 +++++-- uv.lock | 251 +++++++++++++++++++++++---- 7 files changed, 361 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e87877d..33b4a43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## v1.2.0 (2026-01-08) + +### Added + +- New `aws_iam_streamable_http_client` function to replace deprecated `aws_iam_streamablehttp_client` + +### Changed + +- Updated minimum `fastmcp` version to 2.14.2 to support `streamable_http_client` function from mcp>=1.25.0 + +### Deprecated + +- `aws_iam_streamablehttp_client` is now deprecated in favor of `aws_iam_streamable_http_client` + to align with upstream MCP package naming conventions. The old function will be removed in version 2.0.0. +- `sse_read_timeout` parameter in `aws_iam_streamable_http_client` is deprecated and will be removed in version 2.0.0 + ## v1.1.5 (2025-12-15) ### Fix diff --git a/mcp_proxy_for_aws/client.py b/mcp_proxy_for_aws/client.py index efdb6bd..0bde73b 100644 --- a/mcp_proxy_for_aws/client.py +++ b/mcp_proxy_for_aws/client.py @@ -13,12 +13,14 @@ # limitations under the License. import boto3 +import httpx import logging +import warnings from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream from botocore.credentials import Credentials from contextlib import _AsyncGeneratorContextManager from datetime import timedelta -from mcp.client.streamable_http import GetSessionIdCallback, streamablehttp_client +from mcp.client.streamable_http import GetSessionIdCallback, streamable_http_client from mcp.shared._httpx_utils import McpHttpClientFactory, create_mcp_http_client from mcp.shared.message import SessionMessage from mcp_proxy_for_aws.sigv4_helper import SigV4HTTPXAuth @@ -28,7 +30,7 @@ logger = logging.getLogger(__name__) -def aws_iam_streamablehttp_client( +def aws_iam_streamable_http_client( endpoint: str, aws_service: str, aws_region: Optional[str] = None, @@ -60,7 +62,7 @@ def aws_iam_streamablehttp_client( credentials: Optional AWS credentials from boto3/botocore. If provided, takes precedence over aws_profile. headers: Optional additional HTTP headers to include in requests. timeout: Request timeout in seconds or timedelta object. Defaults to 30 seconds. - sse_read_timeout: Server-sent events read timeout in seconds or timedelta object. + sse_read_timeout: Deprecated. This parameter is no longer used and will be removed in version 2.0.0. terminate_on_close: Whether to terminate the connection on close. httpx_client_factory: Factory function for creating HTTPX clients. @@ -71,7 +73,7 @@ def aws_iam_streamablehttp_client( - get_session_id: Callback function to retrieve the current session ID Example: - async with aws_iam_mcp_client( + async with aws_iam_streamable_http_client( endpoint="https://example.com/mcp", aws_service="bedrock-agentcore", aws_region="us-west-2" @@ -81,6 +83,13 @@ def aws_iam_streamablehttp_client( """ logger.debug('Preparing AWS IAM MCP client for endpoint: %s', endpoint) + # Warn if sse_read_timeout is set to a non-default value + if sse_read_timeout != 60 * 5: + logger.warning( + 'sse_read_timeout parameter is deprecated and will be removed in version 2.0.0. ' + 'The value is ignored in the current implementation.' + ) + if credentials is not None: creds = credentials region = aws_region @@ -113,13 +122,74 @@ def aws_iam_streamablehttp_client( # Create a SigV4 authentication handler with AWS credentials auth = SigV4HTTPXAuth(creds, aws_service, region) + # Convert timeout to httpx.Timeout if it's a number or timedelta + httpx_timeout = None + if timeout is not None: + if isinstance(timeout, (int, float)): + httpx_timeout = httpx.Timeout(timeout) + elif isinstance(timeout, timedelta): + httpx_timeout = httpx.Timeout(timeout.total_seconds()) + else: + httpx_timeout = timeout + + # Create HTTP client using the factory with authentication and custom headers + http_client = httpx_client_factory( + auth=auth, + timeout=httpx_timeout, + headers=headers, + ) + # Return the streamable HTTP client context manager with AWS IAM authentication - return streamablehttp_client( + return streamable_http_client( url=endpoint, + http_client=http_client, + terminate_on_close=terminate_on_close, + ) + + +def aws_iam_streamablehttp_client( + endpoint: str, + aws_service: str, + aws_region: Optional[str] = None, + aws_profile: Optional[str] = None, + credentials: Optional[Credentials] = None, + headers: Optional[dict[str, str]] = None, + timeout: float | timedelta = 30, + sse_read_timeout: float | timedelta = 60 * 5, + terminate_on_close: bool = True, + httpx_client_factory: McpHttpClientFactory = create_mcp_http_client, +) -> _AsyncGeneratorContextManager[ + tuple[ + MemoryObjectReceiveStream[SessionMessage | Exception], + MemoryObjectSendStream[SessionMessage], + GetSessionIdCallback, + ], + None, +]: + """Create an AWS IAM-authenticated MCP streamable HTTP client. + + .. deprecated:: 1.2.0 + Use :func:`aws_iam_streamable_http_client` instead. + This function will be removed in version 2.0.0. + + This is a deprecated alias for aws_iam_streamable_http_client. + Please update your code to use aws_iam_streamable_http_client instead. + """ + warnings.warn( + "aws_iam_streamablehttp_client is deprecated and will be removed in version 2.0.0. " + "Use aws_iam_streamable_http_client instead.", + DeprecationWarning, + stacklevel=2, + ) + return aws_iam_streamable_http_client( + endpoint=endpoint, + aws_service=aws_service, + aws_region=aws_region, + aws_profile=aws_profile, + credentials=credentials, headers=headers, timeout=timeout, sse_read_timeout=sse_read_timeout, terminate_on_close=terminate_on_close, httpx_client_factory=httpx_client_factory, - auth=auth, ) diff --git a/mcp_proxy_for_aws/utils.py b/mcp_proxy_for_aws/utils.py index 4c3aeac..5f2c256 100644 --- a/mcp_proxy_for_aws/utils.py +++ b/mcp_proxy_for_aws/utils.py @@ -57,6 +57,7 @@ def client_factory( headers: Optional[Dict[str, str]] = None, timeout: Optional[httpx.Timeout] = None, auth: Optional[httpx.Auth] = None, + **kwargs, # Accept additional parameters from fastmcp (e.g., follow_redirects) ) -> httpx.AsyncClient: return create_sigv4_client( service=service, diff --git a/pyproject.toml b/pyproject.toml index 2ac7670..fc75e53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,13 +10,13 @@ members = [ name = "mcp-proxy-for-aws" # NOTE: "Patch"=9223372036854775807 bumps next release to zero. -version = "1.1.5" +version = "1.2.0" description = "MCP Proxy for AWS" readme = "README.md" requires-python = ">=3.10,<3.14" dependencies = [ - "fastmcp (>=2.13.1,<2.14.1)", + "fastmcp>=2.14.2", "boto3>=1.41.0", "botocore[crt]>=1.41.0", ] diff --git a/tests/integ/mcp/simple_mcp_client.py b/tests/integ/mcp/simple_mcp_client.py index ed73ac8..8dbf47e 100644 --- a/tests/integ/mcp/simple_mcp_client.py +++ b/tests/integ/mcp/simple_mcp_client.py @@ -69,8 +69,11 @@ def _build_mcp_config(endpoint: str, region_name: str, metadata: Optional[Dict[s 'AWS_REGION': region_name, 'AWS_ACCESS_KEY_ID': credentials.access_key, 'AWS_SECRET_ACCESS_KEY': credentials.secret_key, - 'AWS_SESSION_TOKEN': credentials.token, } + + # Only include AWS_SESSION_TOKEN if it's not None (e.g., for temporary credentials) + if credentials.token: + environment_variables['AWS_SESSION_TOKEN'] = credentials.token args = _build_args(endpoint, region_name, metadata) diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 2960e7d..4da9e89 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -60,7 +60,7 @@ async def test_boto3_session_parameters( mock_read, mock_write, mock_get_session = mock_streams with patch('boto3.Session', return_value=mock_session) as mock_boto: - with patch('mcp_proxy_for_aws.client.streamablehttp_client') as mock_stream_client: + with patch('mcp_proxy_for_aws.client.streamable_http_client') as mock_stream_client: mock_stream_client.return_value.__aenter__ = AsyncMock( return_value=(mock_read, mock_write, mock_get_session) ) @@ -94,9 +94,14 @@ async def test_sigv4_auth_is_created_and_used(mock_session, mock_streams, servic with patch('boto3.Session', return_value=mock_session): with patch('mcp_proxy_for_aws.client.SigV4HTTPXAuth') as mock_auth_cls: - with patch('mcp_proxy_for_aws.client.streamablehttp_client') as mock_stream_client: + with patch('mcp_proxy_for_aws.client.streamable_http_client') as mock_stream_client: mock_auth = Mock() mock_auth_cls.return_value = mock_auth + + # Mock the factory to capture its calls + mock_http_client = Mock() + mock_factory = Mock(return_value=mock_http_client) + mock_stream_client.return_value.__aenter__ = AsyncMock( return_value=(mock_read, mock_write, mock_get_session) ) @@ -106,17 +111,22 @@ async def test_sigv4_auth_is_created_and_used(mock_session, mock_streams, servic endpoint='https://test.example.com/mcp', aws_service=service_name, aws_region=region, + httpx_client_factory=mock_factory, ): pass mock_auth_cls.assert_called_once_with( # Auth should be constructed with the resolved credentials, service, and region, - # and passed into the streamable client. + # and passed to the httpx client factory. mock_session.get_credentials.return_value, service_name, region, ) - assert mock_stream_client.call_args[1]['auth'] is mock_auth + # Check that factory was called with auth + assert mock_factory.called + assert mock_factory.call_args[1]['auth'] is mock_auth + # Check that http_client was passed to streamable_http_client + assert mock_stream_client.call_args[1]['http_client'] is mock_http_client @pytest.mark.asyncio @@ -137,7 +147,10 @@ async def test_streamable_client_parameters( mock_read, mock_write, mock_get_session = mock_streams with patch('boto3.Session', return_value=mock_session): - with patch('mcp_proxy_for_aws.client.streamablehttp_client') as mock_stream_client: + with patch('mcp_proxy_for_aws.client.streamable_http_client') as mock_stream_client: + mock_http_client = Mock() + mock_factory = Mock(return_value=mock_http_client) + mock_stream_client.return_value.__aenter__ = AsyncMock( return_value=(mock_read, mock_write, mock_get_session) ) @@ -150,16 +163,30 @@ async def test_streamable_client_parameters( timeout=timeout_value, sse_read_timeout=sse_value, terminate_on_close=terminate_value, + httpx_client_factory=mock_factory, ): pass - call_kwargs = mock_stream_client.call_args[1] - # Confirm each parameter is forwarded unchanged. - assert call_kwargs['url'] == 'https://test.example.com/mcp' - assert call_kwargs['headers'] == headers - assert call_kwargs['timeout'] == timeout_value - assert call_kwargs['sse_read_timeout'] == sse_value - assert call_kwargs['terminate_on_close'] == terminate_value + # Check that factory was called with headers and timeout + assert mock_factory.called + factory_kwargs = mock_factory.call_args[1] + assert factory_kwargs['headers'] == headers + # Check timeout conversion + if isinstance(timeout_value, timedelta): + expected_timeout = timeout_value.total_seconds() + else: + expected_timeout = timeout_value + # httpx.Timeout sets all timeout types (connect, read, write, pool) to the same value + assert factory_kwargs['timeout'].connect == expected_timeout + assert factory_kwargs['timeout'].read == expected_timeout + assert factory_kwargs['timeout'].write == expected_timeout + assert factory_kwargs['timeout'].pool == expected_timeout + + # Check streamable_http_client was called correctly + stream_kwargs = mock_stream_client.call_args[1] + assert stream_kwargs['url'] == 'https://test.example.com/mcp' + assert stream_kwargs['http_client'] is mock_http_client + assert stream_kwargs['terminate_on_close'] == terminate_value @pytest.mark.asyncio @@ -170,7 +197,9 @@ async def test_custom_httpx_client_factory_is_passed(mock_session, mock_streams) custom_factory = Mock() with patch('boto3.Session', return_value=mock_session): - with patch('mcp_proxy_for_aws.client.streamablehttp_client') as mock_stream_client: + with patch('mcp_proxy_for_aws.client.streamable_http_client') as mock_stream_client: + mock_http_client = Mock() + custom_factory.return_value = mock_http_client mock_stream_client.return_value.__aenter__ = AsyncMock( return_value=(mock_read, mock_write, mock_get_session) ) @@ -183,7 +212,10 @@ async def test_custom_httpx_client_factory_is_passed(mock_session, mock_streams) ): pass - assert mock_stream_client.call_args[1]['httpx_client_factory'] is custom_factory + # Check that the custom factory was called + assert custom_factory.called + # Check that the http_client from custom factory was passed to streamable_http_client + assert mock_stream_client.call_args[1]['http_client'] is mock_http_client @pytest.mark.asyncio @@ -198,7 +230,7 @@ async def mock_aexit(*_): cleanup_called = True with patch('boto3.Session', return_value=mock_session): - with patch('mcp_proxy_for_aws.client.streamablehttp_client') as mock_stream_client: + with patch('mcp_proxy_for_aws.client.streamable_http_client') as mock_stream_client: mock_stream_client.return_value.__aenter__ = AsyncMock( return_value=(mock_read, mock_write, mock_get_session) ) @@ -220,7 +252,7 @@ async def test_credentials_parameter_with_region(mock_streams): creds = Credentials('test_key', 'test_secret', 'test_token') with patch('mcp_proxy_for_aws.client.SigV4HTTPXAuth') as mock_auth_cls: - with patch('mcp_proxy_for_aws.client.streamablehttp_client') as mock_stream_client: + with patch('mcp_proxy_for_aws.client.streamable_http_client') as mock_stream_client: mock_auth = Mock() mock_auth_cls.return_value = mock_auth mock_stream_client.return_value.__aenter__ = AsyncMock( @@ -264,7 +296,7 @@ async def test_credentials_parameter_bypasses_boto3_session(mock_streams): with patch('boto3.Session') as mock_boto: with patch('mcp_proxy_for_aws.client.SigV4HTTPXAuth'): - with patch('mcp_proxy_for_aws.client.streamablehttp_client') as mock_stream_client: + with patch('mcp_proxy_for_aws.client.streamable_http_client') as mock_stream_client: mock_stream_client.return_value.__aenter__ = AsyncMock( return_value=(mock_read, mock_write, mock_get_session) ) diff --git a/uv.lock b/uv.lock index 897fc57..f85cdd8 100644 --- a/uv.lock +++ b/uv.lock @@ -840,6 +840,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, ] +[[package]] +name = "cloudpickle" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -1149,6 +1158,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, ] +[[package]] +name = "fakeredis" +version = "2.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "redis" }, + { name = "sortedcontainers" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5f/f9/57464119936414d60697fcbd32f38909bb5688b616ae13de6e98384433e0/fakeredis-2.33.0.tar.gz", hash = "sha256:d7bc9a69d21df108a6451bbffee23b3eba432c21a654afc7ff2d295428ec5770", size = 175187, upload-time = "2025-12-16T19:45:52.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/78/a850fed8aeef96d4a99043c90b818b2ed5419cd5b24a4049fd7cfb9f1471/fakeredis-2.33.0-py3-none-any.whl", hash = "sha256:de535f3f9ccde1c56672ab2fdd6a8efbc4f2619fc2f1acc87b8737177d71c965", size = 119605, upload-time = "2025-12-16T19:45:51.08Z" }, +] + +[package.optional-dependencies] +lua = [ + { name = "lupa" }, +] + [[package]] name = "fastapi" version = "0.122.0" @@ -1166,7 +1194,7 @@ wheels = [ [[package]] name = "fastmcp" -version = "2.13.1" +version = "2.14.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "authlib" }, @@ -1179,15 +1207,16 @@ dependencies = [ { name = "platformdirs" }, { name = "py-key-value-aio", extra = ["disk", "keyring", "memory"] }, { name = "pydantic", extra = ["email"] }, + { name = "pydocket" }, { name = "pyperclip" }, { name = "python-dotenv" }, { name = "rich" }, { name = "uvicorn" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d4/a3/c9eb28b5f0b979b0dd8aa9ba56e69298cdb2d72c15592165d042ccb20194/fastmcp-2.13.1.tar.gz", hash = "sha256:b9c664c51f1ff47c698225e7304267ae29a51913f681bd49e442b8682f9a5f90", size = 8170226, upload-time = "2025-11-15T19:02:17.693Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/1e/e3528227688c248283f6d86869b1e900563ffc223eff00f4f923d2750365/fastmcp-2.14.2.tar.gz", hash = "sha256:bd23d1b808b6f446444f10114dac468b11bfb9153ed78628f5619763d0cf573e", size = 8272966, upload-time = "2025-12-31T15:26:13.433Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/4b/7e36db0a90044be181319ff025be7cc57089ddb6ba8f3712dea543b9cf97/fastmcp-2.13.1-py3-none-any.whl", hash = "sha256:7a78b19785c4ec04a758d920c312769a497e3f6ab4c80feed504df1ed7de9f3c", size = 376750, upload-time = "2025-11-15T19:02:15.748Z" }, + { url = "https://files.pythonhosted.org/packages/0d/67/8456d39484fcb7afd0defed21918e773ed59a98b39e5b633328527c88367/fastmcp-2.14.2-py3-none-any.whl", hash = "sha256:e33cd622e1ebd5110af6a981804525b6cd41072e3c7d68268ed69ef3be651aca", size = 413279, upload-time = "2025-12-31T15:26:11.178Z" }, ] [[package]] @@ -2242,6 +2271,58 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/05/50/c5ccd2a50daa0a10c7f3f7d4e6992392454198cd8a7d99fcb96cb60d0686/llama_parse-0.6.54-py3-none-any.whl", hash = "sha256:c66c8d51cf6f29a44eaa8595a595de5d2598afc86e5a33a4cebe5fe228036920", size = 4879, upload-time = "2025-08-01T20:09:22.651Z" }, ] +[[package]] +name = "lupa" +version = "2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/1c/191c3e6ec6502e3dbe25a53e27f69a5daeac3e56de1f73c0138224171ead/lupa-2.6.tar.gz", hash = "sha256:9a770a6e89576be3447668d7ced312cd6fd41d3c13c2462c9dc2c2ab570e45d9", size = 7240282, upload-time = "2025-10-24T07:20:29.738Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/15/713cab5d0dfa4858f83b99b3e0329072df33dc14fc3ebbaa017e0f9755c4/lupa-2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b3dabda836317e63c5ad052826e156610f356a04b3003dfa0dbe66b5d54d671", size = 954828, upload-time = "2025-10-24T07:17:15.726Z" }, + { url = "https://files.pythonhosted.org/packages/2e/71/704740cbc6e587dd6cc8dabf2f04820ac6a671784e57cc3c29db795476db/lupa-2.6-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8726d1c123bbe9fbb974ce29825e94121824e66003038ff4532c14cc2ed0c51c", size = 1919259, upload-time = "2025-10-24T07:17:18.586Z" }, + { url = "https://files.pythonhosted.org/packages/eb/18/f248341c423c5d48837e35584c6c3eb4acab7e722b6057d7b3e28e42dae8/lupa-2.6-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:f4e159e7d814171199b246f9235ca8961f6461ea8c1165ab428afa13c9289a94", size = 984998, upload-time = "2025-10-24T07:17:20.428Z" }, + { url = "https://files.pythonhosted.org/packages/44/1e/8a4bd471e018aad76bcb9455d298c2c96d82eced20f2ae8fcec8cd800948/lupa-2.6-cp310-cp310-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:202160e80dbfddfb79316692a563d843b767e0f6787bbd1c455f9d54052efa6c", size = 1174871, upload-time = "2025-10-24T07:17:22.755Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5c/3a3f23fd6a91b0986eea1ceaf82ad3f9b958fe3515a9981fb9c4eb046c8b/lupa-2.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5deede7c5b36ab64f869dae4831720428b67955b0bb186c8349cf6ea121c852b", size = 1057471, upload-time = "2025-10-24T07:17:24.908Z" }, + { url = "https://files.pythonhosted.org/packages/45/ac/01be1fed778fb0c8f46ee8cbe344e4d782f6806fac12717f08af87aa4355/lupa-2.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86f04901f920bbf7c0cac56807dc9597e42347123e6f1f3ca920f15f54188ce5", size = 2100592, upload-time = "2025-10-24T07:17:27.089Z" }, + { url = "https://files.pythonhosted.org/packages/3f/6c/1a05bb873e30830f8574e10cd0b4cdbc72e9dbad2a09e25810b5e3b1f75d/lupa-2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6deef8f851d6afb965c84849aa5b8c38856942df54597a811ce0369ced678610", size = 1081396, upload-time = "2025-10-24T07:17:29.064Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c2/a19dd80d6dc98b39bbf8135b8198e38aa7ca3360b720eac68d1d7e9286b5/lupa-2.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:21f2b5549681c2a13b1170a26159d30875d367d28f0247b81ca347222c755038", size = 1192007, upload-time = "2025-10-24T07:17:31.362Z" }, + { url = "https://files.pythonhosted.org/packages/4f/43/e1b297225c827f55752e46fdbfb021c8982081b0f24490e42776ea69ae3b/lupa-2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:66eea57630eab5e6f49fdc5d7811c0a2a41f2011be4ea56a087ea76112011eb7", size = 2196661, upload-time = "2025-10-24T07:17:33.484Z" }, + { url = "https://files.pythonhosted.org/packages/2e/8f/2272d429a7fa9dc8dbd6e9c5c9073a03af6007eb22a4c78829fec6a34b80/lupa-2.6-cp310-cp310-win32.whl", hash = "sha256:60a403de8cab262a4fe813085dd77010effa6e2eb1886db2181df803140533b1", size = 1412738, upload-time = "2025-10-24T07:17:35.11Z" }, + { url = "https://files.pythonhosted.org/packages/35/2a/1708911271dd49ad87b4b373b5a4b0e0a0516d3d2af7b76355946c7ee171/lupa-2.6-cp310-cp310-win_amd64.whl", hash = "sha256:e4656a39d93dfa947cf3db56dc16c7916cb0cc8024acd3a952071263f675df64", size = 1656898, upload-time = "2025-10-24T07:17:36.949Z" }, + { url = "https://files.pythonhosted.org/packages/ca/29/1f66907c1ebf1881735afa695e646762c674f00738ebf66d795d59fc0665/lupa-2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6d988c0f9331b9f2a5a55186701a25444ab10a1432a1021ee58011499ecbbdd5", size = 962875, upload-time = "2025-10-24T07:17:39.107Z" }, + { url = "https://files.pythonhosted.org/packages/e6/67/4a748604be360eb9c1c215f6a0da921cd1a2b44b2c5951aae6fb83019d3a/lupa-2.6-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:ebe1bbf48259382c72a6fe363dea61a0fd6fe19eab95e2ae881e20f3654587bf", size = 1935390, upload-time = "2025-10-24T07:17:41.427Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0c/8ef9ee933a350428b7bdb8335a37ef170ab0bb008bbf9ca8f4f4310116b6/lupa-2.6-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:a8fcee258487cf77cdd41560046843bb38c2e18989cd19671dd1e2596f798306", size = 992193, upload-time = "2025-10-24T07:17:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/65/46/e6c7facebdb438db8a65ed247e56908818389c1a5abbf6a36aab14f1057d/lupa-2.6-cp311-cp311-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:561a8e3be800827884e767a694727ed8482d066e0d6edfcbf423b05e63b05535", size = 1165844, upload-time = "2025-10-24T07:17:45.437Z" }, + { url = "https://files.pythonhosted.org/packages/1c/26/9f1154c6c95f175ccbf96aa96c8f569c87f64f463b32473e839137601a8b/lupa-2.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af880a62d47991cae78b8e9905c008cbfdc4a3a9723a66310c2634fc7644578c", size = 1048069, upload-time = "2025-10-24T07:17:47.181Z" }, + { url = "https://files.pythonhosted.org/packages/68/67/2cc52ab73d6af81612b2ea24c870d3fa398443af8e2875e5befe142398b1/lupa-2.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80b22923aa4023c86c0097b235615f89d469a0c4eee0489699c494d3367c4c85", size = 2079079, upload-time = "2025-10-24T07:17:49.755Z" }, + { url = "https://files.pythonhosted.org/packages/2e/dc/f843f09bbf325f6e5ee61730cf6c3409fc78c010d968c7c78acba3019ca7/lupa-2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:153d2cc6b643f7efb9cfc0c6bb55ec784d5bac1a3660cfc5b958a7b8f38f4a75", size = 1071428, upload-time = "2025-10-24T07:17:51.991Z" }, + { url = "https://files.pythonhosted.org/packages/2e/60/37533a8d85bf004697449acb97ecdacea851acad28f2ad3803662487dd2a/lupa-2.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3fa8777e16f3ded50b72967dc17e23f5a08e4f1e2c9456aff2ebdb57f5b2869f", size = 1181756, upload-time = "2025-10-24T07:17:53.752Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f2/cf29b20dbb4927b6a3d27c339ac5d73e74306ecc28c8e2c900b2794142ba/lupa-2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8dbdcbe818c02a2f56f5ab5ce2de374dab03e84b25266cfbaef237829bc09b3f", size = 2175687, upload-time = "2025-10-24T07:17:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/94/7c/050e02f80c7131b63db1474bff511e63c545b5a8636a24cbef3fc4da20b6/lupa-2.6-cp311-cp311-win32.whl", hash = "sha256:defaf188fde8f7a1e5ce3a5e6d945e533b8b8d547c11e43b96c9b7fe527f56dc", size = 1412592, upload-time = "2025-10-24T07:17:59.062Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/6f2af98aa5d771cea661f66c8eb8f53772ec1ab1dfbce24126cfcd189436/lupa-2.6-cp311-cp311-win_amd64.whl", hash = "sha256:9505ae600b5c14f3e17e70f87f88d333717f60411faca1ddc6f3e61dce85fa9e", size = 1669194, upload-time = "2025-10-24T07:18:01.647Z" }, + { url = "https://files.pythonhosted.org/packages/94/86/ce243390535c39d53ea17ccf0240815e6e457e413e40428a658ea4ee4b8d/lupa-2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47ce718817ef1cc0c40d87c3d5ae56a800d61af00fbc0fad1ca9be12df2f3b56", size = 951707, upload-time = "2025-10-24T07:18:03.884Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/cedea5e6cbeb54396fdcc55f6b741696f3f036d23cfaf986d50d680446da/lupa-2.6-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7aba985b15b101495aa4b07112cdc08baa0c545390d560ad5cfde2e9e34f4d58", size = 1916703, upload-time = "2025-10-24T07:18:05.6Z" }, + { url = "https://files.pythonhosted.org/packages/24/be/3d6b5f9a8588c01a4d88129284c726017b2089f3a3fd3ba8bd977292fea0/lupa-2.6-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:b766f62f95b2739f2248977d29b0722e589dcf4f0ccfa827ccbd29f0148bd2e5", size = 985152, upload-time = "2025-10-24T07:18:08.561Z" }, + { url = "https://files.pythonhosted.org/packages/eb/23/9f9a05beee5d5dce9deca4cb07c91c40a90541fc0a8e09db4ee670da550f/lupa-2.6-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:00a934c23331f94cb51760097ebfab14b005d55a6b30a2b480e3c53dd2fa290d", size = 1159599, upload-time = "2025-10-24T07:18:10.346Z" }, + { url = "https://files.pythonhosted.org/packages/40/4e/e7c0583083db9d7f1fd023800a9767d8e4391e8330d56c2373d890ac971b/lupa-2.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21de9f38bd475303e34a042b7081aabdf50bd9bafd36ce4faea2f90fd9f15c31", size = 1038686, upload-time = "2025-10-24T07:18:12.112Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/5a4f7d959d4feba5e203ff0c31889e74d1ca3153122be4a46dca7d92bf7c/lupa-2.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf3bda96d3fc41237e964a69c23647d50d4e28421111360274d4799832c560e9", size = 2071956, upload-time = "2025-10-24T07:18:14.572Z" }, + { url = "https://files.pythonhosted.org/packages/92/34/2f4f13ca65d01169b1720176aedc4af17bc19ee834598c7292db232cb6dc/lupa-2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a76ead245da54801a81053794aa3975f213221f6542d14ec4b859ee2e7e0323", size = 1057199, upload-time = "2025-10-24T07:18:16.379Z" }, + { url = "https://files.pythonhosted.org/packages/35/2a/5f7d2eebec6993b0dcd428e0184ad71afb06a45ba13e717f6501bfed1da3/lupa-2.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8dd0861741caa20886ddbda0a121d8e52fb9b5bb153d82fa9bba796962bf30e8", size = 1173693, upload-time = "2025-10-24T07:18:18.153Z" }, + { url = "https://files.pythonhosted.org/packages/e4/29/089b4d2f8e34417349af3904bb40bec40b65c8731f45e3fd8d497ca573e5/lupa-2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:239e63948b0b23023f81d9a19a395e768ed3da6a299f84e7963b8f813f6e3f9c", size = 2164394, upload-time = "2025-10-24T07:18:20.403Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1b/79c17b23c921f81468a111cad843b076a17ef4b684c4a8dff32a7969c3f0/lupa-2.6-cp312-cp312-win32.whl", hash = "sha256:325894e1099499e7a6f9c351147661a2011887603c71086d36fe0f964d52d1ce", size = 1420647, upload-time = "2025-10-24T07:18:23.368Z" }, + { url = "https://files.pythonhosted.org/packages/b8/15/5121e68aad3584e26e1425a5c9a79cd898f8a152292059e128c206ee817c/lupa-2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c735a1ce8ee60edb0fe71d665f1e6b7c55c6021f1d340eb8c865952c602cd36f", size = 1688529, upload-time = "2025-10-24T07:18:25.523Z" }, + { url = "https://files.pythonhosted.org/packages/28/1d/21176b682ca5469001199d8b95fa1737e29957a3d185186e7a8b55345f2e/lupa-2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:663a6e58a0f60e7d212017d6678639ac8df0119bc13c2145029dcba084391310", size = 947232, upload-time = "2025-10-24T07:18:27.878Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4c/d327befb684660ca13cf79cd1f1d604331808f9f1b6fb6bf57832f8edf80/lupa-2.6-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:d1f5afda5c20b1f3217a80e9bc1b77037f8a6eb11612fd3ada19065303c8f380", size = 1908625, upload-time = "2025-10-24T07:18:29.944Z" }, + { url = "https://files.pythonhosted.org/packages/66/8e/ad22b0a19454dfd08662237a84c792d6d420d36b061f239e084f29d1a4f3/lupa-2.6-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:26f2b3c085fe76e9119e48c1013c1cccdc1f51585d456858290475aa38e7089e", size = 981057, upload-time = "2025-10-24T07:18:31.553Z" }, + { url = "https://files.pythonhosted.org/packages/5c/48/74859073ab276bd0566c719f9ca0108b0cfc1956ca0d68678d117d47d155/lupa-2.6-cp313-cp313-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:60d2f902c7b96fb8ab98493dcff315e7bb4d0b44dc9dd76eb37de575025d5685", size = 1156227, upload-time = "2025-10-24T07:18:33.981Z" }, + { url = "https://files.pythonhosted.org/packages/09/6c/0e9ded061916877253c2266074060eb71ed99fb21d73c8c114a76725bce2/lupa-2.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a02d25dee3a3250967c36590128d9220ae02f2eda166a24279da0b481519cbff", size = 1035752, upload-time = "2025-10-24T07:18:36.32Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ef/f8c32e454ef9f3fe909f6c7d57a39f950996c37a3deb7b391fec7903dab7/lupa-2.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6eae1ee16b886b8914ff292dbefbf2f48abfbdee94b33a88d1d5475e02423203", size = 2069009, upload-time = "2025-10-24T07:18:38.072Z" }, + { url = "https://files.pythonhosted.org/packages/53/dc/15b80c226a5225815a890ee1c11f07968e0aba7a852df41e8ae6fe285063/lupa-2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0edd5073a4ee74ab36f74fe61450148e6044f3952b8d21248581f3c5d1a58be", size = 1056301, upload-time = "2025-10-24T07:18:40.165Z" }, + { url = "https://files.pythonhosted.org/packages/31/14/2086c1425c985acfb30997a67e90c39457122df41324d3c179d6ee2292c6/lupa-2.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0c53ee9f22a8a17e7d4266ad48e86f43771951797042dd51d1494aaa4f5f3f0a", size = 1170673, upload-time = "2025-10-24T07:18:42.426Z" }, + { url = "https://files.pythonhosted.org/packages/10/e5/b216c054cf86576c0191bf9a9f05de6f7e8e07164897d95eea0078dca9b2/lupa-2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:de7c0f157a9064a400d828789191a96da7f4ce889969a588b87ec80de9b14772", size = 2162227, upload-time = "2025-10-24T07:18:46.112Z" }, + { url = "https://files.pythonhosted.org/packages/59/2f/33ecb5bedf4f3bc297ceacb7f016ff951331d352f58e7e791589609ea306/lupa-2.6-cp313-cp313-win32.whl", hash = "sha256:ee9523941ae0a87b5b703417720c5d78f72d2f5bc23883a2ea80a949a3ed9e75", size = 1419558, upload-time = "2025-10-24T07:18:48.371Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b4/55e885834c847ea610e111d87b9ed4768f0afdaeebc00cd46810f25029f6/lupa-2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b1335a5835b0a25ebdbc75cf0bda195e54d133e4d994877ef025e218c2e59db9", size = 1683424, upload-time = "2025-10-24T07:18:50.976Z" }, +] + [[package]] name = "markdown-it-py" version = "4.0.0" @@ -2331,7 +2412,7 @@ wheels = [ [[package]] name = "mcp" -version = "1.20.0" +version = "1.25.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -2345,11 +2426,13 @@ dependencies = [ { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "sse-starlette" }, { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f8/22/fae38092e6c2995c03232635028510d77e7decff31b4ae79dfa0ba99c635/mcp-1.20.0.tar.gz", hash = "sha256:9ccc09eaadbfbcbbdab1c9723cfe2e0d1d9e324d7d3ce7e332ef90b09ed35177", size = 451377, upload-time = "2025-10-30T22:14:53.421Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/2d/649d80a0ecf6a1f82632ca44bec21c0461a9d9fc8934d38cb5b319f2db5e/mcp-1.25.0.tar.gz", hash = "sha256:56310361ebf0364e2d438e5b45f7668cbb124e158bb358333cd06e49e83a6802", size = 605387, upload-time = "2025-12-19T10:19:56.985Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/00/76fc92f4892d47fecb37131d0e95ea69259f077d84c68f6793a0d96cfe80/mcp-1.20.0-py3-none-any.whl", hash = "sha256:d0dc06f93653f7432ff89f694721c87f79876b6f93741bf628ad1e48f7ac5e5d", size = 173136, upload-time = "2025-10-30T22:14:51.078Z" }, + { url = "https://files.pythonhosted.org/packages/e2/fc/6dc7659c2ae5ddf280477011f4213a74f806862856b796ef08f028e664bf/mcp-1.25.0-py3-none-any.whl", hash = "sha256:b37c38144a666add0862614cc79ec276e97d72aa8ca26d622818d4e278b9721a", size = 233076, upload-time = "2025-12-19T10:19:55.416Z" }, ] [package.optional-dependencies] @@ -2433,7 +2516,7 @@ requires-dist = [ [[package]] name = "mcp-proxy-for-aws" -version = "1.1.5" +version = "1.2.0" source = { editable = "." } dependencies = [ { name = "boto3" }, @@ -2460,7 +2543,7 @@ dev = [ requires-dist = [ { name = "boto3", specifier = ">=1.41.0" }, { name = "botocore", extras = ["crt"], specifier = ">=1.41.0" }, - { name = "fastmcp", specifier = ">=2.13.1,<2.14.1" }, + { name = "fastmcp", specifier = ">=2.14.2" }, ] [package.metadata.requires-dev] @@ -2987,32 +3070,32 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.38.0" +version = "1.39.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/d8/0f354c375628e048bd0570645b310797299754730079853095bf000fba69/opentelemetry_api-1.38.0.tar.gz", hash = "sha256:f4c193b5e8acb0912b06ac5b16321908dd0843d75049c091487322284a3eea12", size = 65242, upload-time = "2025-10-16T08:35:50.25Z" } +sdist = { url = "https://files.pythonhosted.org/packages/97/b9/3161be15bb8e3ad01be8be5a968a9237c3027c5be504362ff800fca3e442/opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c", size = 65767, upload-time = "2025-12-11T13:32:39.182Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/a2/d86e01c28300bd41bab8f18afd613676e2bd63515417b77636fc1add426f/opentelemetry_api-1.38.0-py3-none-any.whl", hash = "sha256:2891b0197f47124454ab9f0cf58f3be33faca394457ac3e09daba13ff50aa582", size = 65947, upload-time = "2025-10-16T08:35:30.23Z" }, + { url = "https://files.pythonhosted.org/packages/cf/df/d3f1ddf4bb4cb50ed9b1139cc7b1c54c34a1e7ce8fd1b9a37c0d1551a6bd/opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950", size = 66356, upload-time = "2025-12-11T13:32:17.304Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.38.0" +version = "1.39.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/83/dd4660f2956ff88ed071e9e0e36e830df14b8c5dc06722dbde1841accbe8/opentelemetry_exporter_otlp_proto_common-1.38.0.tar.gz", hash = "sha256:e333278afab4695aa8114eeb7bf4e44e65c6607d54968271a249c180b2cb605c", size = 20431, upload-time = "2025-10-16T08:35:53.285Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/9d/22d241b66f7bbde88a3bfa6847a351d2c46b84de23e71222c6aae25c7050/opentelemetry_exporter_otlp_proto_common-1.39.1.tar.gz", hash = "sha256:763370d4737a59741c89a67b50f9e39271639ee4afc999dadfe768541c027464", size = 20409, upload-time = "2025-12-11T13:32:40.885Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/9e/55a41c9601191e8cd8eb626b54ee6827b9c9d4a46d736f32abc80d8039fc/opentelemetry_exporter_otlp_proto_common-1.38.0-py3-none-any.whl", hash = "sha256:03cb76ab213300fe4f4c62b7d8f17d97fcfd21b89f0b5ce38ea156327ddda74a", size = 18359, upload-time = "2025-10-16T08:35:34.099Z" }, + { url = "https://files.pythonhosted.org/packages/8c/02/ffc3e143d89a27ac21fd557365b98bd0653b98de8a101151d5805b5d4c33/opentelemetry_exporter_otlp_proto_common-1.39.1-py3-none-any.whl", hash = "sha256:08f8a5862d64cc3435105686d0216c1365dc5701f86844a8cd56597d0c764fde", size = 18366, upload-time = "2025-12-11T13:32:20.2Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-grpc" -version = "1.38.0" +version = "1.39.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, @@ -3023,14 +3106,28 @@ dependencies = [ { name = "opentelemetry-sdk" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/c0/43222f5b97dc10812bc4f0abc5dc7cd0a2525a91b5151d26c9e2e958f52e/opentelemetry_exporter_otlp_proto_grpc-1.38.0.tar.gz", hash = "sha256:2473935e9eac71f401de6101d37d6f3f0f1831db92b953c7dcc912536158ebd6", size = 24676, upload-time = "2025-10-16T08:35:53.83Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/48/b329fed2c610c2c32c9366d9dc597202c9d1e58e631c137ba15248d8850f/opentelemetry_exporter_otlp_proto_grpc-1.39.1.tar.gz", hash = "sha256:772eb1c9287485d625e4dbe9c879898e5253fea111d9181140f51291b5fec3ad", size = 24650, upload-time = "2025-12-11T13:32:41.429Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/a3/cc9b66575bd6597b98b886a2067eea2693408d2d5f39dad9ab7fc264f5f3/opentelemetry_exporter_otlp_proto_grpc-1.39.1-py3-none-any.whl", hash = "sha256:fa1c136a05c7e9b4c09f739469cbdb927ea20b34088ab1d959a849b5cc589c18", size = 19766, upload-time = "2025-12-11T13:32:21.027Z" }, +] + +[[package]] +name = "opentelemetry-exporter-prometheus" +version = "0.60b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, + { name = "prometheus-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/14/39/7dafa6fff210737267bed35a8855b6ac7399b9e582b8cf1f25f842517012/opentelemetry_exporter_prometheus-0.60b1.tar.gz", hash = "sha256:a4011b46906323f71724649d301b4dc188aaa068852e814f4df38cc76eac616b", size = 14976, upload-time = "2025-12-11T13:32:42.944Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/f0/bd831afbdba74ca2ce3982142a2fad707f8c487e8a3b6fef01f1d5945d1b/opentelemetry_exporter_otlp_proto_grpc-1.38.0-py3-none-any.whl", hash = "sha256:7c49fd9b4bd0dbe9ba13d91f764c2d20b0025649a6e4ac35792fb8d84d764bc7", size = 19695, upload-time = "2025-10-16T08:35:35.053Z" }, + { url = "https://files.pythonhosted.org/packages/9b/0d/4be6bf5477a3eb3d917d2f17d3c0b6720cd6cb97898444a61d43cc983f5c/opentelemetry_exporter_prometheus-0.60b1-py3-none-any.whl", hash = "sha256:49f59178de4f4590e3cef0b8b95cf6e071aae70e1f060566df5546fad773b8fd", size = 13019, upload-time = "2025-12-11T13:32:23.974Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.59b0" +version = "0.60b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -3038,62 +3135,62 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/04/ed/9c65cd209407fd807fa05be03ee30f159bdac8d59e7ea16a8fe5a1601222/opentelemetry_instrumentation-0.59b0.tar.gz", hash = "sha256:6010f0faaacdaf7c4dff8aac84e226d23437b331dcda7e70367f6d73a7db1adc", size = 31544, upload-time = "2025-10-16T08:39:31.959Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/0f/7e6b713ac117c1f5e4e3300748af699b9902a2e5e34c9cf443dde25a01fa/opentelemetry_instrumentation-0.60b1.tar.gz", hash = "sha256:57ddc7974c6eb35865af0426d1a17132b88b2ed8586897fee187fd5b8944bd6a", size = 31706, upload-time = "2025-12-11T13:36:42.515Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/f5/7a40ff3f62bfe715dad2f633d7f1174ba1a7dd74254c15b2558b3401262a/opentelemetry_instrumentation-0.59b0-py3-none-any.whl", hash = "sha256:44082cc8fe56b0186e87ee8f7c17c327c4c2ce93bdbe86496e600985d74368ee", size = 33020, upload-time = "2025-10-16T08:38:31.463Z" }, + { url = "https://files.pythonhosted.org/packages/77/d2/6788e83c5c86a2690101681aeef27eeb2a6bf22df52d3f263a22cee20915/opentelemetry_instrumentation-0.60b1-py3-none-any.whl", hash = "sha256:04480db952b48fb1ed0073f822f0ee26012b7be7c3eac1a3793122737c78632d", size = 33096, upload-time = "2025-12-11T13:35:33.067Z" }, ] [[package]] name = "opentelemetry-instrumentation-threading" -version = "0.59b0" +version = "0.60b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-instrumentation" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/82/7a/84e97d8992808197006e607ae410c2219bdbbc23d1289ba0c244d3220741/opentelemetry_instrumentation_threading-0.59b0.tar.gz", hash = "sha256:ce5658730b697dcbc0e0d6d13643a69fd8aeb1b32fa8db3bade8ce114c7975f3", size = 8770, upload-time = "2025-10-16T08:40:03.587Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/0a/e36123ec4c0910a3936b92982545a53e9bca5b26a28df06883751a783f84/opentelemetry_instrumentation_threading-0.60b1.tar.gz", hash = "sha256:20b18a68abe5801fa9474336b7c27487d4af3e00b66f6a8734e4fdd75c8b0b43", size = 8768, upload-time = "2025-12-11T13:37:16.29Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/50/32d29076aaa1c91983cdd3ca8c6bb4d344830cd7d87a7c0fdc2d98c58509/opentelemetry_instrumentation_threading-0.59b0-py3-none-any.whl", hash = "sha256:76da2fc01fe1dccebff6581080cff9e42ac7b27cc61eb563f3c4435c727e8eca", size = 9313, upload-time = "2025-10-16T08:39:15.876Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a3/448738b927bcc1843ace7d4ed55dd54441a71363075eeeee89c5944dd740/opentelemetry_instrumentation_threading-0.60b1-py3-none-any.whl", hash = "sha256:92a52a60fee5e32bc6aa8f5acd749b15691ad0bc4457a310f5736b76a6d9d1de", size = 9312, upload-time = "2025-12-11T13:36:28.434Z" }, ] [[package]] name = "opentelemetry-proto" -version = "1.38.0" +version = "1.39.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/14/f0c4f0f6371b9cb7f9fa9ee8918bfd59ac7040c7791f1e6da32a1839780d/opentelemetry_proto-1.38.0.tar.gz", hash = "sha256:88b161e89d9d372ce723da289b7da74c3a8354a8e5359992be813942969ed468", size = 46152, upload-time = "2025-10-16T08:36:01.612Z" } +sdist = { url = "https://files.pythonhosted.org/packages/49/1d/f25d76d8260c156c40c97c9ed4511ec0f9ce353f8108ca6e7561f82a06b2/opentelemetry_proto-1.39.1.tar.gz", hash = "sha256:6c8e05144fc0d3ed4d22c2289c6b126e03bcd0e6a7da0f16cedd2e1c2772e2c8", size = 46152, upload-time = "2025-12-11T13:32:48.681Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/6a/82b68b14efca5150b2632f3692d627afa76b77378c4999f2648979409528/opentelemetry_proto-1.38.0-py3-none-any.whl", hash = "sha256:b6ebe54d3217c42e45462e2a1ae28c3e2bf2ec5a5645236a490f55f45f1a0a18", size = 72535, upload-time = "2025-10-16T08:35:45.749Z" }, + { url = "https://files.pythonhosted.org/packages/51/95/b40c96a7b5203005a0b03d8ce8cd212ff23f1793d5ba289c87a097571b18/opentelemetry_proto-1.39.1-py3-none-any.whl", hash = "sha256:22cdc78efd3b3765d09e68bfbd010d4fc254c9818afd0b6b423387d9dee46007", size = 72535, upload-time = "2025-12-11T13:32:33.866Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.38.0" +version = "1.39.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/85/cb/f0eee1445161faf4c9af3ba7b848cc22a50a3d3e2515051ad8628c35ff80/opentelemetry_sdk-1.38.0.tar.gz", hash = "sha256:93df5d4d871ed09cb4272305be4d996236eedb232253e3ab864c8620f051cebe", size = 171942, upload-time = "2025-10-16T08:36:02.257Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/fb/c76080c9ba07e1e8235d24cdcc4d125ef7aa3edf23eb4e497c2e50889adc/opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6", size = 171460, upload-time = "2025-12-11T13:32:49.369Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/2e/e93777a95d7d9c40d270a371392b6d6f1ff170c2a3cb32d6176741b5b723/opentelemetry_sdk-1.38.0-py3-none-any.whl", hash = "sha256:1c66af6564ecc1553d72d811a01df063ff097cdc82ce188da9951f93b8d10f6b", size = 132349, upload-time = "2025-10-16T08:35:46.995Z" }, + { url = "https://files.pythonhosted.org/packages/7c/98/e91cf858f203d86f4eccdf763dcf01cf03f1dae80c3750f7e635bfa206b6/opentelemetry_sdk-1.39.1-py3-none-any.whl", hash = "sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c", size = 132565, upload-time = "2025-12-11T13:32:35.069Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.59b0" +version = "0.60b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/bc/8b9ad3802cd8ac6583a4eb7de7e5d7db004e89cb7efe7008f9c8a537ee75/opentelemetry_semantic_conventions-0.59b0.tar.gz", hash = "sha256:7a6db3f30d70202d5bf9fa4b69bc866ca6a30437287de6c510fb594878aed6b0", size = 129861, upload-time = "2025-10-16T08:36:03.346Z" } +sdist = { url = "https://files.pythonhosted.org/packages/91/df/553f93ed38bf22f4b999d9be9c185adb558982214f33eae539d3b5cd0858/opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953", size = 137935, upload-time = "2025-12-11T13:32:50.487Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/24/7d/c88d7b15ba8fe5c6b8f93be50fc11795e9fc05386c44afaf6b76fe191f9b/opentelemetry_semantic_conventions-0.59b0-py3-none-any.whl", hash = "sha256:35d3b8833ef97d614136e253c1da9342b4c3c083bbaf29ce31d572a1c3825eed", size = 207954, upload-time = "2025-10-16T08:35:48.054Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5e/5958555e09635d09b75de3c4f8b9cae7335ca545d77392ffe7331534c402/opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb", size = 219982, upload-time = "2025-12-11T13:32:36.955Z" }, ] [[package]] @@ -3444,6 +3541,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/11/574fe7d13acf30bfd0a8dd7fa1647040f2b8064f13f43e8c963b1e65093b/pre_commit-4.4.0-py2.py3-none-any.whl", hash = "sha256:b35ea52957cbf83dcc5d8ee636cbead8624e3a15fbfa61a370e42158ac8a5813", size = 226049, upload-time = "2025-11-08T21:12:10.228Z" }, ] +[[package]] +name = "prometheus-client" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/53/3edb5d68ecf6b38fcbcc1ad28391117d2a322d9a1a3eff04bfdb184d8c3b/prometheus_client-0.23.1.tar.gz", hash = "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce", size = 80481, upload-time = "2025-09-18T20:47:25.043Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145, upload-time = "2025-09-18T20:47:23.875Z" }, +] + [[package]] name = "prompt-toolkit" version = "3.0.51" @@ -3568,15 +3674,15 @@ wheels = [ [[package]] name = "py-key-value-aio" -version = "0.2.8" +version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beartype" }, { name = "py-key-value-shared" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/35/65310a4818acec0f87a46e5565e341c5a96fc062a9a03495ad28828ff4d7/py_key_value_aio-0.2.8.tar.gz", hash = "sha256:c0cfbb0bd4e962a3fa1a9fa6db9ba9df812899bd9312fa6368aaea7b26008b36", size = 32853, upload-time = "2025-10-24T13:31:04.688Z" } +sdist = { url = "https://files.pythonhosted.org/packages/93/ce/3136b771dddf5ac905cc193b461eb67967cf3979688c6696e1f2cdcde7ea/py_key_value_aio-0.3.0.tar.gz", hash = "sha256:858e852fcf6d696d231266da66042d3355a7f9871650415feef9fca7a6cd4155", size = 50801, upload-time = "2025-11-17T16:50:04.711Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/5a/e56747d87a97ad2aff0f3700d77f186f0704c90c2da03bfed9e113dae284/py_key_value_aio-0.2.8-py3-none-any.whl", hash = "sha256:561565547ce8162128fd2bd0b9d70ce04a5f4586da8500cce79a54dfac78c46a", size = 69200, upload-time = "2025-10-24T13:31:03.81Z" }, + { url = "https://files.pythonhosted.org/packages/99/10/72f6f213b8f0bce36eff21fda0a13271834e9eeff7f9609b01afdc253c79/py_key_value_aio-0.3.0-py3-none-any.whl", hash = "sha256:1c781915766078bfd608daa769fefb97e65d1d73746a3dfb640460e322071b64", size = 96342, upload-time = "2025-11-17T16:50:03.801Z" }, ] [package.optional-dependencies] @@ -3590,18 +3696,21 @@ keyring = [ memory = [ { name = "cachetools" }, ] +redis = [ + { name = "redis" }, +] [[package]] name = "py-key-value-shared" -version = "0.2.8" +version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beartype" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/26/79/05a1f9280cfa0709479319cbfd2b1c5beb23d5034624f548c83fb65b0b61/py_key_value_shared-0.2.8.tar.gz", hash = "sha256:703b4d3c61af124f0d528ba85995c3c8d78f8bd3d2b217377bd3278598070cc1", size = 8216, upload-time = "2025-10-24T13:31:03.601Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/e4/1971dfc4620a3a15b4579fe99e024f5edd6e0967a71154771a059daff4db/py_key_value_shared-0.3.0.tar.gz", hash = "sha256:8fdd786cf96c3e900102945f92aa1473138ebe960ef49da1c833790160c28a4b", size = 11666, upload-time = "2025-11-17T16:50:06.849Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/7a/1726ceaa3343874f322dd83c9ec376ad81f533df8422b8b1e1233a59f8ce/py_key_value_shared-0.2.8-py3-none-any.whl", hash = "sha256:aff1bbfd46d065b2d67897d298642e80e5349eae588c6d11b48452b46b8d46ba", size = 14586, upload-time = "2025-10-24T13:31:02.838Z" }, + { url = "https://files.pythonhosted.org/packages/51/e4/b8b0a03ece72f47dce2307d36e1c34725b7223d209fc679315ffe6a4e2c3/py_key_value_shared-0.3.0-py3-none-any.whl", hash = "sha256:5b0efba7ebca08bb158b1e93afc2f07d30b8f40c2fc12ce24a4c0d84f42f9298", size = 19560, upload-time = "2025-11-17T16:50:05.954Z" }, ] [[package]] @@ -3763,6 +3872,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/d6/887a1ff844e64aa823fb4905978d882a633cfe295c32eacad582b78a7d8b/pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c", size = 48608, upload-time = "2025-09-24T14:19:10.015Z" }, ] +[[package]] +name = "pydocket" +version = "0.16.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cloudpickle" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "fakeredis", extra = ["lua"] }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-prometheus" }, + { name = "opentelemetry-instrumentation" }, + { name = "prometheus-client" }, + { name = "py-key-value-aio", extra = ["memory", "redis"] }, + { name = "python-json-logger" }, + { name = "redis" }, + { name = "rich" }, + { name = "typer" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/c5/61dcfce4d50b66a3f09743294d37fab598b81bb0975054b7f732da9243ec/pydocket-0.16.3.tar.gz", hash = "sha256:78e9da576de09e9f3f410d2471ef1c679b7741ddd21b586c97a13872b69bd265", size = 297080, upload-time = "2025-12-23T23:37:33.32Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/94/93b7f5981aa04f922e0d9ce7326a4587866ec7e39f7c180ffcf408e66ee8/pydocket-0.16.3-py3-none-any.whl", hash = "sha256:e2b50925356e7cd535286255195458ac7bba15f25293356651b36d223db5dd7c", size = 67087, upload-time = "2025-12-23T23:37:31.829Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -3899,6 +4032,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, ] +[[package]] +name = "python-json-logger" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683, upload-time = "2025-10-06T04:15:18.984Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548, upload-time = "2025-10-06T04:15:17.553Z" }, +] + [[package]] name = "python-multipart" version = "0.0.20" @@ -4383,6 +4525,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, ] +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -4401,6 +4552,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + [[package]] name = "soupsieve" version = "2.8" @@ -4635,6 +4795,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, ] +[[package]] +name = "typer" +version = "0.21.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/bf/8825b5929afd84d0dabd606c67cd57b8388cb3ec385f7ef19c5cc2202069/typer-0.21.1.tar.gz", hash = "sha256:ea835607cd752343b6b2b7ce676893e5a0324082268b48f27aa058bdb7d2145d", size = 110371, upload-time = "2026-01-06T11:21:10.989Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl", hash = "sha256:7985e89081c636b88d172c2ee0cfe33c253160994d47bdfdc302defd7d1f1d01", size = 47381, upload-time = "2026-01-06T11:21:09.824Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" From 24f4bccff8b24f5f1fff8377f997a48e6640861f Mon Sep 17 00:00:00 2001 From: Andrey Yoshua Date: Fri, 9 Jan 2026 03:47:24 +0700 Subject: [PATCH 2/2] Updated `aws_iam_streamable_http_client` signature --- CHANGELOG.md | 8 +- mcp_proxy_for_aws/client.py | 161 ++++++++++++--------- tests/unit/test_client.py | 272 +++++++++++++++++++++++++++++++++++- 3 files changed, 372 insertions(+), 69 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33b4a43..c7599d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## v1.2.0 (2026-01-08) +## v1.2.0 (2026-01-09) ### Added @@ -14,12 +14,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Updated minimum `fastmcp` version to 2.14.2 to support `streamable_http_client` function from mcp>=1.25.0 +- **BREAKING**: Updated `aws_iam_streamable_http_client` signature to match upstream MCP patterns: + - Removed `headers`, `timeout`, `sse_read_timeout`, and `httpx_client_factory` parameters + - Added `http_client: httpx.AsyncClient | None` parameter for passing pre-configured clients + - Added `*` to make `http_client` and `terminate_on_close` keyword-only arguments + - Changed return type from `_AsyncGeneratorContextManager` to `AsyncGenerator` for consistency with upstream ### Deprecated - `aws_iam_streamablehttp_client` is now deprecated in favor of `aws_iam_streamable_http_client` to align with upstream MCP package naming conventions. The old function will be removed in version 2.0.0. -- `sse_read_timeout` parameter in `aws_iam_streamable_http_client` is deprecated and will be removed in version 2.0.0 ## v1.1.5 (2025-12-15) diff --git a/mcp_proxy_for_aws/client.py b/mcp_proxy_for_aws/client.py index 0bde73b..b8915e6 100644 --- a/mcp_proxy_for_aws/client.py +++ b/mcp_proxy_for_aws/client.py @@ -15,33 +15,32 @@ import boto3 import httpx import logging -import warnings from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream from botocore.credentials import Credentials -from contextlib import _AsyncGeneratorContextManager +from collections.abc import AsyncGenerator from datetime import timedelta from mcp.client.streamable_http import GetSessionIdCallback, streamable_http_client from mcp.shared._httpx_utils import McpHttpClientFactory, create_mcp_http_client from mcp.shared.message import SessionMessage from mcp_proxy_for_aws.sigv4_helper import SigV4HTTPXAuth -from typing import Optional +from typing_extensions import deprecated +from contextlib import asynccontextmanager logger = logging.getLogger(__name__) -def aws_iam_streamable_http_client( +@asynccontextmanager +async def aws_iam_streamable_http_client( endpoint: str, aws_service: str, - aws_region: Optional[str] = None, - aws_profile: Optional[str] = None, - credentials: Optional[Credentials] = None, - headers: Optional[dict[str, str]] = None, - timeout: float | timedelta = 30, - sse_read_timeout: float | timedelta = 60 * 5, + aws_region: str | None = None, + aws_profile: str | None = None, + credentials: Credentials | None = None, + *, + http_client: httpx.AsyncClient | None = None, terminate_on_close: bool = True, - httpx_client_factory: McpHttpClientFactory = create_mcp_http_client, -) -> _AsyncGeneratorContextManager[ +) -> AsyncGenerator[ tuple[ MemoryObjectReceiveStream[SessionMessage | Exception], MemoryObjectSendStream[SessionMessage], @@ -60,14 +59,11 @@ def aws_iam_streamable_http_client( aws_region: The AWS region name of the MCP server, e.g. "us-west-2". aws_profile: The AWS profile to use for authentication. credentials: Optional AWS credentials from boto3/botocore. If provided, takes precedence over aws_profile. - headers: Optional additional HTTP headers to include in requests. - timeout: Request timeout in seconds or timedelta object. Defaults to 30 seconds. - sse_read_timeout: Deprecated. This parameter is no longer used and will be removed in version 2.0.0. + http_client: Optional pre-configured httpx.AsyncClient. If not provided, one will be created with SigV4 auth. terminate_on_close: Whether to terminate the connection on close. - httpx_client_factory: Factory function for creating HTTPX clients. Returns: - An async generator context manager that yields a tuple of transport components: + An async generator yielding a tuple containing: - read_stream: MemoryObjectReceiveStream for reading server responses - write_stream: MemoryObjectSendStream for sending requests to server - get_session_id: Callback function to retrieve the current session ID @@ -83,13 +79,18 @@ def aws_iam_streamable_http_client( """ logger.debug('Preparing AWS IAM MCP client for endpoint: %s', endpoint) - # Warn if sse_read_timeout is set to a non-default value - if sse_read_timeout != 60 * 5: - logger.warning( - 'sse_read_timeout parameter is deprecated and will be removed in version 2.0.0. ' - 'The value is ignored in the current implementation.' - ) - + # If http_client is provided, use it directly + if http_client is not None: + logger.debug('Using provided http_client') + async with streamable_http_client( + url=endpoint, + http_client=http_client, + terminate_on_close=terminate_on_close, + ) as streams: + yield streams + return + + # Otherwise, create http_client with AWS IAM authentication if credentials is not None: creds = credentials region = aws_region @@ -122,43 +123,35 @@ def aws_iam_streamable_http_client( # Create a SigV4 authentication handler with AWS credentials auth = SigV4HTTPXAuth(creds, aws_service, region) - # Convert timeout to httpx.Timeout if it's a number or timedelta - httpx_timeout = None - if timeout is not None: - if isinstance(timeout, (int, float)): - httpx_timeout = httpx.Timeout(timeout) - elif isinstance(timeout, timedelta): - httpx_timeout = httpx.Timeout(timeout.total_seconds()) - else: - httpx_timeout = timeout - - # Create HTTP client using the factory with authentication and custom headers - http_client = httpx_client_factory( + # Create HTTP client with AWS IAM authentication + client = httpx.AsyncClient( auth=auth, - timeout=httpx_timeout, - headers=headers, + headers={'Accept': 'application/json, text/event-stream'}, ) # Return the streamable HTTP client context manager with AWS IAM authentication - return streamable_http_client( + async with streamable_http_client( url=endpoint, - http_client=http_client, + http_client=client, terminate_on_close=terminate_on_close, - ) + ) as streams: + yield streams -def aws_iam_streamablehttp_client( +@asynccontextmanager +@deprecated("Use `aws_iam_streamable_http_client` instead.") +async def aws_iam_streamablehttp_client( endpoint: str, aws_service: str, - aws_region: Optional[str] = None, - aws_profile: Optional[str] = None, - credentials: Optional[Credentials] = None, - headers: Optional[dict[str, str]] = None, + aws_region: str | None = None, + aws_profile: str | None = None, + credentials: Credentials | None = None, + headers: dict[str, str] | None = None, timeout: float | timedelta = 30, sse_read_timeout: float | timedelta = 60 * 5, terminate_on_close: bool = True, httpx_client_factory: McpHttpClientFactory = create_mcp_http_client, -) -> _AsyncGeneratorContextManager[ +) -> AsyncGenerator[ tuple[ MemoryObjectReceiveStream[SessionMessage | Exception], MemoryObjectSendStream[SessionMessage], @@ -168,28 +161,68 @@ def aws_iam_streamablehttp_client( ]: """Create an AWS IAM-authenticated MCP streamable HTTP client. - .. deprecated:: 1.2.0 - Use :func:`aws_iam_streamable_http_client` instead. - This function will be removed in version 2.0.0. - This is a deprecated alias for aws_iam_streamable_http_client. Please update your code to use aws_iam_streamable_http_client instead. + + This function maintains backward compatibility by accepting the legacy parameters + and creating a properly configured httpx.AsyncClient to pass to the new implementation. """ - warnings.warn( - "aws_iam_streamablehttp_client is deprecated and will be removed in version 2.0.0. " - "Use aws_iam_streamable_http_client instead.", - DeprecationWarning, - stacklevel=2, + # Resolve credentials and region + if credentials is not None: + creds = credentials + region = aws_region + if not region: + raise ValueError( + 'AWS region must be specified via aws_region parameter when using credentials.' + ) + else: + kwargs = {} + if aws_profile is not None: + kwargs['profile_name'] = aws_profile + if aws_region is not None: + kwargs['region_name'] = aws_region + + session = boto3.Session(**kwargs) + creds = session.get_credentials() + region = session.region_name + + if not region: + raise ValueError( + 'AWS region must be specified via aws_region parameter, AWS_REGION environment variable, or AWS config.' + ) + + # Create SigV4 authentication + auth = SigV4HTTPXAuth(creds, aws_service, region) + + # Convert timeout to httpx.Timeout + if isinstance(timeout, timedelta): + timeout_seconds = timeout.total_seconds() + else: + timeout_seconds = timeout + + if isinstance(sse_read_timeout, timedelta): + sse_timeout_seconds = sse_read_timeout.total_seconds() + else: + sse_timeout_seconds = sse_read_timeout + + httpx_timeout = httpx.Timeout(timeout_seconds, read=sse_timeout_seconds) + + # Create httpx client using the factory with legacy parameters + http_client = httpx_client_factory( + headers=headers, + timeout=httpx_timeout, + auth=auth, ) - return aws_iam_streamable_http_client( + + # Delegate to the new function with the configured client + async with aws_iam_streamable_http_client( endpoint=endpoint, aws_service=aws_service, - aws_region=aws_region, + aws_region=region, aws_profile=aws_profile, - credentials=credentials, - headers=headers, - timeout=timeout, - sse_read_timeout=sse_read_timeout, + credentials=creds, + http_client=http_client, terminate_on_close=terminate_on_close, - httpx_client_factory=httpx_client_factory, - ) + ) as streams: + # Yield the streams tuple - @asynccontextmanager handles the rest + yield streams diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 4da9e89..33fbc78 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -17,7 +17,7 @@ import pytest from botocore.credentials import Credentials from datetime import timedelta -from mcp_proxy_for_aws.client import aws_iam_streamablehttp_client +from mcp_proxy_for_aws.client import aws_iam_streamablehttp_client, aws_iam_streamable_http_client from unittest.mock import AsyncMock, Mock, patch @@ -176,9 +176,13 @@ async def test_streamable_client_parameters( expected_timeout = timeout_value.total_seconds() else: expected_timeout = timeout_value - # httpx.Timeout sets all timeout types (connect, read, write, pool) to the same value + if isinstance(sse_value, timedelta): + expected_sse_timeout = sse_value.total_seconds() + else: + expected_sse_timeout = sse_value + # httpx.Timeout - all except read should be timeout_value, read should be sse_read_timeout assert factory_kwargs['timeout'].connect == expected_timeout - assert factory_kwargs['timeout'].read == expected_timeout + assert factory_kwargs['timeout'].read == expected_sse_timeout assert factory_kwargs['timeout'].write == expected_timeout assert factory_kwargs['timeout'].pool == expected_timeout @@ -311,3 +315,265 @@ async def test_credentials_parameter_bypasses_boto3_session(mock_streams): pass mock_boto.assert_not_called() + + +# Tests for the new aws_iam_streamable_http_client function + + +@pytest.mark.asyncio +async def test_new_client_with_http_client_provided(mock_streams): + """Test that providing http_client uses it directly without creating auth.""" + mock_read, mock_write, mock_get_session = mock_streams + mock_http_client = Mock() + + with patch('mcp_proxy_for_aws.client.streamable_http_client') as mock_stream_client: + with patch('boto3.Session') as mock_boto: + with patch('mcp_proxy_for_aws.client.SigV4HTTPXAuth') as mock_auth_cls: + mock_stream_client.return_value.__aenter__ = AsyncMock( + return_value=(mock_read, mock_write, mock_get_session) + ) + mock_stream_client.return_value.__aexit__ = AsyncMock(return_value=None) + + async with aws_iam_streamable_http_client( + endpoint='https://test.example.com/mcp', + aws_service='bedrock-agentcore', + aws_region='us-west-2', + http_client=mock_http_client, + ): + pass + + # Should not create boto3 session or auth when http_client is provided + mock_boto.assert_not_called() + mock_auth_cls.assert_not_called() + + # Should pass the provided client to streamable_http_client + assert mock_stream_client.call_args[1]['http_client'] is mock_http_client + assert mock_stream_client.call_args[1]['url'] == 'https://test.example.com/mcp' + + +@pytest.mark.asyncio +async def test_new_client_with_credentials_and_region(mock_streams): + """Test the new client with provided credentials and region.""" + mock_read, mock_write, mock_get_session = mock_streams + creds = Credentials('new_key', 'new_secret', 'new_token') + + with patch('mcp_proxy_for_aws.client.SigV4HTTPXAuth') as mock_auth_cls: + with patch('mcp_proxy_for_aws.client.streamable_http_client') as mock_stream_client: + with patch('httpx.AsyncClient') as mock_client_cls: + mock_auth = Mock() + mock_auth_cls.return_value = mock_auth + mock_client = Mock() + mock_client_cls.return_value = mock_client + + mock_stream_client.return_value.__aenter__ = AsyncMock( + return_value=(mock_read, mock_write, mock_get_session) + ) + mock_stream_client.return_value.__aexit__ = AsyncMock(return_value=None) + + async with aws_iam_streamable_http_client( + endpoint='https://new.example.com/mcp', + aws_service='execute-api', + aws_region='eu-west-1', + credentials=creds, + ): + pass + + # Should create auth with provided credentials + mock_auth_cls.assert_called_once_with(creds, 'execute-api', 'eu-west-1') + + # Should create httpx client with auth and default headers + mock_client_cls.assert_called_once_with( + auth=mock_auth, + headers={'Accept': 'application/json, text/event-stream'} + ) + + # Should pass the created client to streamable_http_client + assert mock_stream_client.call_args[1]['http_client'] is mock_client + + +@pytest.mark.asyncio +async def test_new_client_without_credentials_uses_boto3(mock_streams): + """Test that new client without credentials uses boto3.Session.""" + mock_read, mock_write, mock_get_session = mock_streams + + # Create a custom mock session for this test + mock_session = Mock() + credentials = Mock() + credentials.access_key = 'test_access_key' + credentials.secret_key = 'test_secret_key' + credentials.token = 'test_token' + mock_session.get_credentials.return_value = credentials + mock_session.profile_name = 'test-profile' + mock_session.region_name = 'ap-south-1' # Use the region we're testing with + + with patch('boto3.Session', return_value=mock_session) as mock_boto: + with patch('mcp_proxy_for_aws.client.SigV4HTTPXAuth') as mock_auth_cls: + with patch('mcp_proxy_for_aws.client.streamable_http_client') as mock_stream_client: + with patch('httpx.AsyncClient'): + mock_auth = Mock() + mock_auth_cls.return_value = mock_auth + + mock_stream_client.return_value.__aenter__ = AsyncMock( + return_value=(mock_read, mock_write, mock_get_session) + ) + mock_stream_client.return_value.__aexit__ = AsyncMock(return_value=None) + + async with aws_iam_streamable_http_client( + endpoint='https://test.example.com/mcp', + aws_service='bedrock-agentcore', + aws_region='ap-south-1', + aws_profile='test-profile', + ): + pass + + # Should create boto3 session with region and profile + mock_boto.assert_called_once_with( + region_name='ap-south-1', + profile_name='test-profile' + ) + + # Should use credentials from session + mock_auth_cls.assert_called_once_with( + mock_session.get_credentials.return_value, + 'bedrock-agentcore', + 'ap-south-1' + ) + + +@pytest.mark.asyncio +async def test_new_client_credentials_without_region_raises(): + """Test that new client with credentials but no region raises ValueError.""" + creds = Credentials('key', 'secret', 'token') + + with pytest.raises( + ValueError, + match='AWS region must be specified via aws_region parameter when using credentials' + ): + async with aws_iam_streamable_http_client( + endpoint='https://test.example.com/mcp', + aws_service='bedrock-agentcore', + credentials=creds, + ): + pass + + +@pytest.mark.asyncio +async def test_new_client_without_region_in_session_raises(): + """Test that new client raises when region cannot be determined from session.""" + mock_session = Mock() + mock_session.get_credentials.return_value = Mock() + mock_session.region_name = None + + with pytest.raises( + ValueError, + match='AWS region must be specified via aws_region parameter, AWS_REGION environment variable, or AWS config' + ): + with patch('boto3.Session', return_value=mock_session): + async with aws_iam_streamable_http_client( + endpoint='https://test.example.com/mcp', + aws_service='bedrock-agentcore', + ): + pass + + +@pytest.mark.asyncio +async def test_new_client_terminate_on_close_parameter(mock_streams): + """Test that terminate_on_close parameter is passed correctly.""" + mock_read, mock_write, mock_get_session = mock_streams + mock_http_client = Mock() + + with patch('mcp_proxy_for_aws.client.streamable_http_client') as mock_stream_client: + mock_stream_client.return_value.__aenter__ = AsyncMock( + return_value=(mock_read, mock_write, mock_get_session) + ) + mock_stream_client.return_value.__aexit__ = AsyncMock(return_value=None) + + # Test with terminate_on_close=False + async with aws_iam_streamable_http_client( + endpoint='https://test.example.com/mcp', + aws_service='bedrock-agentcore', + aws_region='us-west-2', + http_client=mock_http_client, + terminate_on_close=False, + ): + pass + + assert mock_stream_client.call_args[1]['terminate_on_close'] is False + + +@pytest.mark.asyncio +async def test_new_client_returns_streams_tuple(mock_session, mock_streams): + """Test that new client returns the correct streams tuple.""" + mock_read, mock_write, mock_get_session = mock_streams + + with patch('boto3.Session', return_value=mock_session): + with patch('mcp_proxy_for_aws.client.streamable_http_client') as mock_stream_client: + with patch('httpx.AsyncClient'): + mock_stream_client.return_value.__aenter__ = AsyncMock( + return_value=(mock_read, mock_write, mock_get_session) + ) + mock_stream_client.return_value.__aexit__ = AsyncMock(return_value=None) + + async with aws_iam_streamable_http_client( + endpoint='https://test.example.com/mcp', + aws_service='bedrock-agentcore', + ) as (read_stream, write_stream, get_session_id): + assert read_stream is mock_read + assert write_stream is mock_write + assert get_session_id is mock_get_session + + +@pytest.mark.asyncio +async def test_new_client_logging_debug_messages(mock_session, mock_streams): + """Test that new client logs appropriate debug messages.""" + mock_read, mock_write, mock_get_session = mock_streams + + with patch('boto3.Session', return_value=mock_session): + with patch('mcp_proxy_for_aws.client.streamable_http_client') as mock_stream_client: + with patch('httpx.AsyncClient'): + with patch('mcp_proxy_for_aws.client.logger') as mock_logger: + mock_stream_client.return_value.__aenter__ = AsyncMock( + return_value=(mock_read, mock_write, mock_get_session) + ) + mock_stream_client.return_value.__aexit__ = AsyncMock(return_value=None) + + async with aws_iam_streamable_http_client( + endpoint='https://test.example.com/mcp', + aws_service='my-service', + aws_region='my-region', + ): + pass + + # Check that debug logging was called + assert mock_logger.debug.called + # Verify specific log messages + debug_calls = [call[0][0] for call in mock_logger.debug.call_args_list] + assert any('Preparing AWS IAM MCP client' in msg for msg in debug_calls) + assert any('AWS region' in msg for msg in debug_calls) + assert any('AWS service' in msg for msg in debug_calls) + + +@pytest.mark.asyncio +async def test_new_client_with_provided_http_client_logs_correctly(mock_streams): + """Test that providing http_client logs the appropriate message.""" + mock_read, mock_write, mock_get_session = mock_streams + mock_http_client = Mock() + + with patch('mcp_proxy_for_aws.client.streamable_http_client') as mock_stream_client: + with patch('mcp_proxy_for_aws.client.logger') as mock_logger: + mock_stream_client.return_value.__aenter__ = AsyncMock( + return_value=(mock_read, mock_write, mock_get_session) + ) + mock_stream_client.return_value.__aexit__ = AsyncMock(return_value=None) + + async with aws_iam_streamable_http_client( + endpoint='https://test.example.com/mcp', + aws_service='bedrock-agentcore', + aws_region='us-west-2', + http_client=mock_http_client, + ): + pass + + # Should log about using provided http_client + debug_calls = [call[0][0] for call in mock_logger.debug.call_args_list] + assert any('Using provided http_client' in msg for msg in debug_calls)