diff --git a/src/fetch/article.json b/src/fetch/article.json new file mode 100644 index 0000000000..3cd84364f4 --- /dev/null +++ b/src/fetch/article.json @@ -0,0 +1 @@ +{"title":"","byline":null,"dir":null,"content":"

fetch server on mcp 2.0 works

","textContent":"fetch server on mcp 2.0 works","length":29,"excerpt":"fetch server on mcp 2.0 works"} \ No newline at end of file diff --git a/src/fetch/full.html b/src/fetch/full.html new file mode 100644 index 0000000000..8c91fb9cfc --- /dev/null +++ b/src/fetch/full.html @@ -0,0 +1 @@ +

Hello

fetch server on mcp 2.0 works

\ No newline at end of file diff --git a/src/fetch/pyproject.toml b/src/fetch/pyproject.toml index 84735f278a..0cc28d929e 100644 --- a/src/fetch/pyproject.toml +++ b/src/fetch/pyproject.toml @@ -18,7 +18,7 @@ classifiers = [ dependencies = [ "httpx>=0.27", "markdownify>=0.13.1", - "mcp>=1.1.3", + "mcp>=2,<3", "protego>=0.3.1", "pydantic>=2.0.0", "readabilipy>=0.2.0", diff --git a/src/fetch/src/mcp_server_fetch/server.py b/src/fetch/src/mcp_server_fetch/server.py index b42c7b1f6b..a250d981e6 100644 --- a/src/fetch/src/mcp_server_fetch/server.py +++ b/src/fetch/src/mcp_server_fetch/server.py @@ -3,12 +3,17 @@ import markdownify import readabilipy.simple_json -from mcp.shared.exceptions import McpError -from mcp.server import Server +from mcp.shared.exceptions import MCPError +from mcp.server import Server, ServerRequestContext from mcp.server.stdio import stdio_server from mcp.types import ( - ErrorData, + CallToolRequestParams, + CallToolResult, + GetPromptRequestParams, GetPromptResult, + ListPromptsResult, + ListToolsResult, + PaginatedRequestParams, Prompt, PromptArgument, PromptMessage, @@ -66,7 +71,7 @@ def get_robots_txt_url(url: str) -> str: async def check_may_autonomously_fetch_url(url: str, user_agent: str, proxy_url: str | None = None) -> None: """ Check if the URL can be fetched by the user agent according to the robots.txt file. - Raises a McpError if not. + Raises an MCPError if not. """ from httpx import AsyncClient, HTTPError @@ -80,15 +85,15 @@ async def check_may_autonomously_fetch_url(url: str, user_agent: str, proxy_url: headers={"User-Agent": user_agent}, ) except HTTPError: - raise McpError(ErrorData( + raise MCPError( code=INTERNAL_ERROR, message=f"Failed to fetch robots.txt {robot_txt_url} due to a connection issue", - )) + ) if response.status_code in (401, 403): - raise McpError(ErrorData( + raise MCPError( code=INTERNAL_ERROR, message=f"When fetching robots.txt ({robot_txt_url}), received status {response.status_code} so assuming that autonomous fetching is not allowed, the user can try manually fetching by using the fetch prompt", - )) + ) elif 400 <= response.status_code < 500: return robot_txt = response.text @@ -97,7 +102,7 @@ async def check_may_autonomously_fetch_url(url: str, user_agent: str, proxy_url: ) robot_parser = Protego.parse(processed_robot_txt) if not robot_parser.can_fetch(str(url), user_agent): - raise McpError(ErrorData( + raise MCPError( code=INTERNAL_ERROR, message=f"The sites robots.txt ({robot_txt_url}), specifies that autonomous fetching of this page is not allowed, " f"{user_agent}\n" @@ -105,7 +110,7 @@ async def check_may_autonomously_fetch_url(url: str, user_agent: str, proxy_url: f"\n{robot_txt}\n\n" f"The assistant must let the user know that it failed to view the page. The assistant may provide further guidance based on the above information.\n" f"The assistant can tell the user that they can try manually fetching the page by using the fetch prompt within their UI.", - )) + ) async def fetch_url( @@ -125,12 +130,12 @@ async def fetch_url( timeout=30, ) except HTTPError as e: - raise McpError(ErrorData(code=INTERNAL_ERROR, message=f"Failed to fetch {url}: {e!r}")) + raise MCPError(code=INTERNAL_ERROR, message=f"Failed to fetch {url}: {e!r}") if response.status_code >= 400: - raise McpError(ErrorData( + raise MCPError( code=INTERNAL_ERROR, message=f"Failed to fetch {url} - status code {response.status_code}", - )) + ) page_raw = response.text @@ -190,46 +195,52 @@ async def serve( ignore_robots_txt: Whether to ignore robots.txt restrictions proxy_url: Optional proxy URL to use for requests """ - server = Server("mcp-fetch") user_agent_autonomous = custom_user_agent or DEFAULT_USER_AGENT_AUTONOMOUS user_agent_manual = custom_user_agent or DEFAULT_USER_AGENT_MANUAL - @server.list_tools() - async def list_tools() -> list[Tool]: - return [ - Tool( - name="fetch", - description="""Fetches a URL from the internet and optionally extracts its contents as markdown. + async def list_tools( + ctx: ServerRequestContext, params: PaginatedRequestParams | None + ) -> ListToolsResult: + return ListToolsResult( + tools=[ + Tool( + name="fetch", + description="""Fetches a URL from the internet and optionally extracts its contents as markdown. Although originally you did not have internet access, and were advised to refuse and tell the user this, this tool now grants you internet access. Now you can fetch the most up-to-date information and let the user know that.""", - inputSchema=Fetch.model_json_schema(), - ) - ] - - @server.list_prompts() - async def list_prompts() -> list[Prompt]: - return [ - Prompt( - name="fetch", - description="Fetch a URL and extract its contents as markdown", - arguments=[ - PromptArgument( - name="url", description="URL to fetch", required=True - ) - ], - ) - ] + input_schema=Fetch.model_json_schema(), + ) + ] + ) + + async def list_prompts( + ctx: ServerRequestContext, params: PaginatedRequestParams | None + ) -> ListPromptsResult: + return ListPromptsResult( + prompts=[ + Prompt( + name="fetch", + description="Fetch a URL and extract its contents as markdown", + arguments=[ + PromptArgument( + name="url", description="URL to fetch", required=True + ) + ], + ) + ] + ) - @server.call_tool() - async def call_tool(name, arguments: dict) -> list[TextContent]: + async def call_tool( + ctx: ServerRequestContext, params: CallToolRequestParams + ) -> CallToolResult: try: - args = Fetch(**arguments) + args = Fetch(**(params.arguments or {})) except ValueError as e: - raise McpError(ErrorData(code=INVALID_PARAMS, message=str(e))) + raise MCPError(code=INVALID_PARAMS, message=str(e)) url = str(args.url) if not url: - raise McpError(ErrorData(code=INVALID_PARAMS, message="URL is required")) + raise MCPError(code=INVALID_PARAMS, message="URL is required") if not ignore_robots_txt: await check_may_autonomously_fetch_url(url, user_agent_autonomous, proxy_url) @@ -252,19 +263,23 @@ async def call_tool(name, arguments: dict) -> list[TextContent]: if actual_content_length == args.max_length and remaining_content > 0: next_start = args.start_index + actual_content_length content += f"\n\nContent truncated. Call the fetch tool with a start_index of {next_start} to get more content." - return [TextContent(type="text", text=f"{prefix}Contents of {url}:\n{content}")] + return CallToolResult( + content=[TextContent(type="text", text=f"{prefix}Contents of {url}:\n{content}")] + ) - @server.get_prompt() - async def get_prompt(name: str, arguments: dict | None) -> GetPromptResult: + async def get_prompt( + ctx: ServerRequestContext, params: GetPromptRequestParams + ) -> GetPromptResult: + arguments = params.arguments if not arguments or "url" not in arguments: - raise McpError(ErrorData(code=INVALID_PARAMS, message="URL is required")) + raise MCPError(code=INVALID_PARAMS, message="URL is required") url = arguments["url"] try: content, prefix = await fetch_url(url, user_agent_manual, proxy_url=proxy_url) # TODO: after SDK bug is addressed, don't catch the exception - except McpError as e: + except MCPError as e: return GetPromptResult( description=f"Failed to fetch {url}", messages=[ @@ -283,6 +298,14 @@ async def get_prompt(name: str, arguments: dict | None) -> GetPromptResult: ], ) + server = Server( + "mcp-fetch", + on_list_tools=list_tools, + on_list_prompts=list_prompts, + on_call_tool=call_tool, + on_get_prompt=get_prompt, + ) + options = server.create_initialization_options() async with stdio_server() as (read_stream, write_stream): await server.run(read_stream, write_stream, options, raise_exceptions=False) diff --git a/src/fetch/tests/test_server.py b/src/fetch/tests/test_server.py index 96c1cb38c7..9d4636d497 100644 --- a/src/fetch/tests/test_server.py +++ b/src/fetch/tests/test_server.py @@ -2,7 +2,7 @@ import pytest from unittest.mock import AsyncMock, patch, MagicMock -from mcp.shared.exceptions import McpError +from mcp.shared.exceptions import MCPError from mcp_server_fetch.server import ( extract_content_from_html, @@ -121,7 +121,7 @@ async def test_blocks_when_robots_txt_401(self): mock_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client) mock_client_class.return_value.__aexit__ = AsyncMock(return_value=None) - with pytest.raises(McpError): + with pytest.raises(MCPError): await check_may_autonomously_fetch_url( "https://example.com/page", DEFAULT_USER_AGENT_AUTONOMOUS @@ -139,7 +139,7 @@ async def test_blocks_when_robots_txt_403(self): mock_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client) mock_client_class.return_value.__aexit__ = AsyncMock(return_value=None) - with pytest.raises(McpError): + with pytest.raises(MCPError): await check_may_autonomously_fetch_url( "https://example.com/page", DEFAULT_USER_AGENT_AUTONOMOUS @@ -177,7 +177,7 @@ async def test_blocks_when_robots_txt_disallows_all(self): mock_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client) mock_client_class.return_value.__aexit__ = AsyncMock(return_value=None) - with pytest.raises(McpError): + with pytest.raises(MCPError): await check_may_autonomously_fetch_url( "https://example.com/page", DEFAULT_USER_AGENT_AUTONOMOUS @@ -268,7 +268,7 @@ async def test_fetch_json_returns_raw(self): @pytest.mark.asyncio async def test_fetch_404_raises_error(self): - """Test that 404 response raises McpError.""" + """Test that 404 response raises MCPError.""" mock_response = MagicMock() mock_response.status_code = 404 @@ -278,7 +278,7 @@ async def test_fetch_404_raises_error(self): mock_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client) mock_client_class.return_value.__aexit__ = AsyncMock(return_value=None) - with pytest.raises(McpError): + with pytest.raises(MCPError): await fetch_url( "https://example.com/notfound", DEFAULT_USER_AGENT_AUTONOMOUS @@ -286,7 +286,7 @@ async def test_fetch_404_raises_error(self): @pytest.mark.asyncio async def test_fetch_500_raises_error(self): - """Test that 500 response raises McpError.""" + """Test that 500 response raises MCPError.""" mock_response = MagicMock() mock_response.status_code = 500 @@ -296,7 +296,7 @@ async def test_fetch_500_raises_error(self): mock_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client) mock_client_class.return_value.__aexit__ = AsyncMock(return_value=None) - with pytest.raises(McpError): + with pytest.raises(MCPError): await fetch_url( "https://example.com/error", DEFAULT_USER_AGENT_AUTONOMOUS diff --git a/src/fetch/uv.lock b/src/fetch/uv.lock index ea9b0567e2..48103ad621 100644 --- a/src/fetch/uv.lock +++ b/src/fetch/uv.lock @@ -1,6 +1,10 @@ version = 1 revision = 3 requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version < '3.14'", +] [[package]] name = "annotated-types" @@ -13,17 +17,16 @@ wheels = [ [[package]] name = "anyio" -version = "4.6.2.post1" +version = "4.14.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, - { name = "sniffio" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422, upload-time = "2024-10-14T14:31:44.021Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377, upload-time = "2024-10-14T14:31:42.623Z" }, + { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, ] [[package]] @@ -330,6 +333,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] +[[package]] +name = "httpcore2" +version = "2.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, + { name = "truststore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/a8/20ed1ed79cbc2ecdf5301c0968ab7c85547212e2a7bd126ddd2d986e206e/httpcore2-2.9.1.tar.gz", hash = "sha256:4d8acbf8b306f48c9d6046591fd5ba4037d1b1b1000d140fc2c3eab1e9a0c0e2", size = 67089, upload-time = "2026-07-24T09:21:03.867Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/fb/46c52b781975c335a2bcf1072c7bbc007cbdc8d674217f5ee1daba2c848b/httpcore2-2.9.1-py3-none-any.whl", hash = "sha256:6182472379e855fe4221246a2bb7ecede403bc61c6798062ae1787d051ccde26", size = 82809, upload-time = "2026-07-24T09:21:01.178Z" }, +] + [[package]] name = "httpx" version = "0.27.2" @@ -347,12 +363,19 @@ wheels = [ ] [[package]] -name = "httpx-sse" -version = "0.4.0" +name = "httpx2" +version = "2.9.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624, upload-time = "2023-12-22T08:01:21.083Z" } +dependencies = [ + { name = "anyio" }, + { name = "httpcore2" }, + { name = "idna" }, + { name = "truststore" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/14/38128fbafd7e0ed41d874df6c9a653d47c2d111cfe59e2b4ac95161b4abd/httpx2-2.9.1.tar.gz", hash = "sha256:1932a768737e3666291582833da748cc4e563c337cf96706fccc04fa6e58764a", size = 95458, upload-time = "2026-07-24T09:21:04.972Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819, upload-time = "2023-12-22T08:01:19.89Z" }, + { url = "https://files.pythonhosted.org/packages/13/b8/cfd91c4ab9134d386d48f0b6ac662ff3d4be6efdee59ee1c67ebc3c0487c/httpx2-2.9.1-py3-none-any.whl", hash = "sha256:1820fe14a9ab1107bfeff39259987429450b070ec0ff38cc87eb0d8c97fdc71a", size = 91191, upload-time = "2026-07-24T09:21:02.6Z" }, ] [[package]] @@ -533,15 +556,15 @@ wheels = [ [[package]] name = "mcp" -version = "1.23.0" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, - { name = "httpx" }, - { name = "httpx-sse" }, + { name = "httpx2" }, { name = "jsonschema" }, + { name = "mcp-types" }, + { name = "opentelemetry-api" }, { name = "pydantic" }, - { name = "pydantic-settings" }, { name = "pyjwt", extra = ["crypto"] }, { name = "python-multipart" }, { name = "pywin32", marker = "sys_platform == 'win32'" }, @@ -551,9 +574,9 @@ dependencies = [ { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/1a/9c8a5362e3448d585081d6c7aa95898a64e0ac59d3e26169ae6c3ca5feaf/mcp-1.23.0.tar.gz", hash = "sha256:84e0c29316d0a8cf0affd196fd000487ac512aa3f771b63b2ea864e22961772b", size = 596506, upload-time = "2025-12-02T13:40:02.558Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/33/32d4dff2c95bb5d897c3ef4c83649a08996b17b58f0a326d2495d4c81179/mcp-2.0.0.tar.gz", hash = "sha256:0f440e735c13ece8bb19bc62cf0b86f4313448432fbb77d35e14034f4e050728", size = 1662284, upload-time = "2026-07-28T13:45:32.346Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/b2/28739ce409f98159c0121eab56e69ad71546c4f34ac8b42e58c03f57dccc/mcp-1.23.0-py3-none-any.whl", hash = "sha256:5a645cf111ed329f4619f2629a3f15d9aabd7adc2ea09d600d31467b51ecb64f", size = 231427, upload-time = "2025-12-02T13:40:00.738Z" }, + { url = "https://files.pythonhosted.org/packages/67/72/7d7897418912c1d12e87556630dfb7bf0eac71160e9bef8b447960804ee3/mcp-2.0.0-py3-none-any.whl", hash = "sha256:1cb4c75d2d2c7b8c1d756355e5d82a39f2822cc7f13e22a2051d7ca3592349d6", size = 349980, upload-time = "2026-07-28T13:45:28.853Z" }, ] [[package]] @@ -582,7 +605,7 @@ dev = [ requires-dist = [ { name = "httpx", specifier = ">=0.27" }, { name = "markdownify", specifier = ">=0.13.1" }, - { name = "mcp", specifier = ">=1.1.3" }, + { name = "mcp", specifier = ">=2,<3" }, { name = "protego", specifier = ">=0.3.1" }, { name = "pydantic", specifier = ">=2.0.0" }, { name = "readabilipy", specifier = ">=0.2.0" }, @@ -597,6 +620,19 @@ dev = [ { name = "ruff", specifier = ">=0.7.3" }, ] +[[package]] +name = "mcp-types" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/56/9b8e1c152f61f6c6b07c4b5896c88c7d0ae90bac6ee6306f852fcc5c1eb0/mcp_types-2.0.0.tar.gz", hash = "sha256:d7d939b9285c9961ae8866ba75ef85da34d12bafe276efbf4eb6a131786d8379", size = 66632, upload-time = "2026-07-28T13:45:33.804Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/4c/c78d78c3d52b0ac594ad7cc8ef5972adfe070e3597a8a4c6ce0cd39196ea/mcp_types-2.0.0-py3-none-any.whl", hash = "sha256:6b2de797ca2797f568b79529e1b25948e34de511bcc0bd82fef1039a6d1b8eb0", size = 69649, upload-time = "2026-07-28T13:45:30.713Z" }, +] + [[package]] name = "nodeenv" version = "1.9.1" @@ -606,6 +642,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, ] +[[package]] +name = "opentelemetry-api" +version = "1.44.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/8b/aa9e2d8b8dfa7c946f7dec5d1f8f6ba8eca062f43509a06bdb5ce93d26c0/opentelemetry_api-1.44.0.tar.gz", hash = "sha256:67647e5e9566edcf421166fdf022b3537f818635daa852b289e34604dc6fb33a", size = 72406, upload-time = "2026-07-16T15:25:32.678Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/6f/a04e900f465ff3221ccc395522503e2d10e79fa21f2723c8e177aae1e0d1/opentelemetry_api-1.44.0-py3-none-any.whl", hash = "sha256:94b98c893a91b88657eaac1e3ba89618cdb85be6918196705354f34728b2cdef", size = 60018, upload-time = "2026-07-16T15:25:11.657Z" }, +] + [[package]] name = "packaging" version = "26.0" @@ -775,19 +823,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, ] -[[package]] -name = "pydantic-settings" -version = "2.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b5/d4/9dfbe238f45ad8b168f5c96ee49a3df0598ce18a0795a983b419949ce65b/pydantic_settings-2.6.1.tar.gz", hash = "sha256:e0f92546d8a9923cb8941689abf85d6601a8c19a23e97a34b2964a2e3f813ca0", size = 75646, upload-time = "2024-11-01T11:00:05.17Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/f9/ff95fd7d760af42f647ea87f9b8a383d891cdb5e5dbd4613edaeb094252a/pydantic_settings-2.6.1-py3-none-any.whl", hash = "sha256:7fb0637c786a558d3103436278a7c4f1cfd29ba8973238a50c5bb9a55387da87", size = 28595, upload-time = "2024-11-01T11:00:02.64Z" }, -] - [[package]] name = "pygments" version = "2.20.0" @@ -859,15 +894,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, ] -[[package]] -name = "python-dotenv" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, -] - [[package]] name = "python-multipart" version = "0.0.32" @@ -1188,16 +1214,15 @@ wheels = [ [[package]] name = "sse-starlette" -version = "2.1.3" +version = "3.4.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "starlette" }, - { name = "uvicorn" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/fc/56ab9f116b2133521f532fce8d03194cf04dcac25f583cf3d839be4c0496/sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169", size = 19678, upload-time = "2024-08-01T08:52:50.248Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/10/a34c656829ffc1c4b22ef36d70d9ebb6b99c020e2aeb17cee5485099f028/sse_starlette-3.4.6.tar.gz", hash = "sha256:725f8a1bd6d26ae1b2c9610c0ef5065dfdd496f3988d28adcf8c4b49dc25c627", size = 32542, upload-time = "2026-07-20T14:16:32.201Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/aa/36b271bc4fa1d2796311ee7c7283a3a1c348bad426d37293609ca4300eef/sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772", size = 9383, upload-time = "2024-08-01T08:52:48.659Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/e10c1d1b7ca881d2625db2ec28508578499187bb1c389952c398474e1834/sse_starlette-3.4.6-py3-none-any.whl", hash = "sha256:56217ab4c9a9f9c5db7b21e08732d3e7c2b807f45231ad23de0551a24c4a41f6", size = 16516, upload-time = "2026-07-20T14:16:30.978Z" }, ] [[package]] @@ -1267,6 +1292,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, ] +[[package]] +name = "truststore" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0"