fix: restore MCP v2 integration (#133)

This commit is contained in:
XiaoQing235
2026-08-28 10:44:50 +08:00
committed by GitHub
parent 0763d457d5
commit 13a7cb75f6
4 changed files with 107 additions and 61 deletions
+20 -29
View File
@@ -5,12 +5,12 @@ from datetime import timedelta
from typing import Annotated, Any, Literal from typing import Annotated, Any, Literal
from urllib.parse import quote, unquote from urllib.parse import quote, unquote
import httpx import httpx2
from mcp import ClientSession from mcp.client import Client
from mcp.client.streamable_http import streamablehttp_client from mcp.client.streamable_http import streamable_http_client
from mcp.server.auth.provider import AccessToken from mcp.server.auth.provider import AccessToken
from mcp.server.fastmcp import Context, FastMCP from mcp.server.auth.settings import AuthSettings
from mcp.server.fastmcp.server import AuthSettings from mcp.server.mcpserver import Context, MCPServer
from mcp.types import ToolAnnotations from mcp.types import ToolAnnotations
from pydantic import Field from pydantic import Field
@@ -119,10 +119,9 @@ class FoxelMcpTokenVerifier:
return AccessToken(token=token, client_id=user.username, scopes=[]) return AccessToken(token=token, client_id=user.username, scopes=[])
MCP_SERVER = FastMCP( MCP_SERVER = MCPServer(
name="Foxel MCP", name="Foxel MCP",
instructions="Foxel 内置 MCP 服务,提供文件系统、网页抓取、时间与处理器相关能力。", instructions="Foxel 内置 MCP 服务,提供文件系统、网页抓取、时间与处理器相关能力。",
streamable_http_path="/",
token_verifier=FoxelMcpTokenVerifier(), token_verifier=FoxelMcpTokenVerifier(),
auth=AuthSettings( auth=AuthSettings(
issuer_url="http://127.0.0.1:8000", issuer_url="http://127.0.0.1:8000",
@@ -268,21 +267,7 @@ def fetch_web_page_prompt(url: Annotated[str, Field(description="目标网址")]
return [{"role": "user", "content": f"请抓取网页 `{url}`,并总结标题、正文与关键链接。必要时调用 web_fetch。"}] return [{"role": "user", "content": f"请抓取网页 `{url}`,并总结标题、正文与关键链接。必要时调用 web_fetch。"}]
MCP_HTTP_APP = MCP_SERVER.streamable_http_app() MCP_HTTP_APP = MCP_SERVER.streamable_http_app(streamable_http_path="/")
def loopback_httpx_client_factory(app):
def factory(headers: dict[str, str] | None = None, timeout=None, auth=None) -> httpx.AsyncClient:
return httpx.AsyncClient(
transport=httpx.ASGITransport(app=app),
base_url=INTERNAL_MCP_BASE_URL.rstrip("/"),
headers=headers,
timeout=timeout,
auth=auth,
follow_redirects=True,
)
return factory
async def create_loopback_mcp_headers(user: User | None, current_path: str | None = None) -> dict[str, str]: async def create_loopback_mcp_headers(user: User | None, current_path: str | None = None) -> dict[str, str]:
@@ -301,14 +286,20 @@ async def create_loopback_mcp_headers(user: User | None, current_path: str | Non
@asynccontextmanager @asynccontextmanager
async def mcp_client_session(user: User | None, current_path: str | None = None): async def mcp_client_session(user: User | None, current_path: str | None = None):
headers = await create_loopback_mcp_headers(user, current_path) headers = await create_loopback_mcp_headers(user, current_path)
async with streamablehttp_client( timeout = httpx2.Timeout(30.0, read=300.0)
INTERNAL_MCP_BASE_URL, async with httpx2.AsyncClient(
transport=httpx2.ASGITransport(app=MCP_HTTP_APP),
base_url=INTERNAL_MCP_BASE_URL.rstrip("/"),
headers=headers, headers=headers,
httpx_client_factory=loopback_httpx_client_factory(MCP_HTTP_APP), timeout=timeout,
) as (read_stream, write_stream, _): follow_redirects=True,
async with ClientSession(read_stream, write_stream) as session: ) as http_client:
await session.initialize() transport = streamable_http_client(
yield session INTERNAL_MCP_BASE_URL,
http_client=http_client,
)
async with Client(transport, mode="2026-07-28") as client:
yield client
def mcp_content_to_text(content: list[Any], structured_content: dict[str, Any] | None = None) -> str: def mcp_content_to_text(content: list[Any], structured_content: dict[str, Any] | None = None) -> str:
+2 -2
View File
@@ -149,7 +149,7 @@ async def _list_mcp_tools(session) -> List[Dict[str, Any]]:
{ {
"name": str(getattr(item, "name", "") or ""), "name": str(getattr(item, "name", "") or ""),
"description": str(getattr(item, "description", "") or ""), "description": str(getattr(item, "description", "") or ""),
"input_schema": getattr(item, "inputSchema", None) or {}, "input_schema": getattr(item, "input_schema", None) or {},
"annotations": annotations.model_dump(exclude_none=True) if annotations is not None else {}, "annotations": annotations.model_dump(exclude_none=True) if annotations is not None else {},
"meta": meta if isinstance(meta, dict) else {}, "meta": meta if isinstance(meta, dict) else {},
} }
@@ -159,7 +159,7 @@ async def _list_mcp_tools(session) -> List[Dict[str, Any]]:
async def _execute_mcp_call(session, name: str, arguments: Dict[str, Any]) -> str: async def _execute_mcp_call(session, name: str, arguments: Dict[str, Any]) -> str:
result = await session.call_tool(name, arguments) result = await session.call_tool(name, arguments)
return mcp_content_to_text(result.content, result.structuredContent) return mcp_content_to_text(result.content, result.structured_content)
class AgentService: class AgentService:
+3 -1
View File
@@ -9,7 +9,9 @@ dependencies = [
"bcrypt>=5.0.0", "bcrypt>=5.0.0",
"croniter>=6.0.0", "croniter>=6.0.0",
"fastapi>=0.127.0", "fastapi>=0.127.0",
"mcp>=1.28.1", "mcp>=2.1.1,<3",
"httpx>=0.28.1,<1",
"httpx2>=2.5,<3",
"paramiko>=5.0.0", "paramiko>=5.0.0",
"pillow>=12.3.0", "pillow>=12.3.0",
"pydantic[email]>=2.12.5", "pydantic[email]>=2.12.5",
Generated
+82 -29
View File
@@ -452,6 +452,8 @@ dependencies = [
{ name = "bcrypt" }, { name = "bcrypt" },
{ name = "croniter" }, { name = "croniter" },
{ name = "fastapi" }, { name = "fastapi" },
{ name = "httpx" },
{ name = "httpx2" },
{ name = "mcp" }, { name = "mcp" },
{ name = "paramiko" }, { name = "paramiko" },
{ name = "pillow" }, { name = "pillow" },
@@ -475,7 +477,9 @@ requires-dist = [
{ name = "bcrypt", specifier = ">=5.0.0" }, { name = "bcrypt", specifier = ">=5.0.0" },
{ name = "croniter", specifier = ">=6.0.0" }, { name = "croniter", specifier = ">=6.0.0" },
{ name = "fastapi", specifier = ">=0.127.0" }, { name = "fastapi", specifier = ">=0.127.0" },
{ name = "mcp", specifier = ">=1.28.1" }, { name = "httpx", specifier = ">=0.28.1,<1" },
{ name = "httpx2", specifier = ">=2.5,<3" },
{ name = "mcp", specifier = ">=2.1.1,<3" },
{ name = "paramiko", specifier = ">=5.0.0" }, { name = "paramiko", specifier = ">=5.0.0" },
{ name = "pillow", specifier = ">=12.3.0" }, { name = "pillow", specifier = ">=12.3.0" },
{ name = "pydantic", extras = ["email"], specifier = ">=2.12.5" }, { name = "pydantic", extras = ["email"], specifier = ">=2.12.5" },
@@ -598,6 +602,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" }, { 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.12.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "h11" },
{ name = "truststore" },
]
sdist = { url = "https://files.pythonhosted.org/packages/be/ad/f4f0e57345f1870f3e8cb624e058d7eca6e5a27d33bcc3311d9b618734cd/httpcore2-2.12.0.tar.gz", hash = "sha256:9293522bba0aa7c4c8e9e3f040c16575bd8868e155a77fa30c7a9085a5eae648", size = 67548, upload-time = "2026-08-18T13:22:08.211Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d2/74/d370e55600d9bcfa0d9794b0166126d49291a3d2b20c268fc98c453a4948/httpcore2-2.12.0-py3-none-any.whl", hash = "sha256:7e04258ce01013d7d615e5b910a3b27fac937d7a95038227e79652b4ba3b4ceb", size = 83074, upload-time = "2026-08-18T13:22:05.854Z" },
]
[[package]] [[package]]
name = "httpx" name = "httpx"
version = "0.28.1" version = "0.28.1"
@@ -619,12 +636,28 @@ http2 = [
] ]
[[package]] [[package]]
name = "httpx-sse" name = "httpx2"
version = "0.4.3" version = "2.12.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" } dependencies = [
{ name = "anyio", marker = "sys_platform != 'emscripten'" },
{ name = "httpcore2", marker = "sys_platform != 'emscripten'" },
{ name = "httpx2-jsfetch", marker = "sys_platform == 'emscripten'" },
{ name = "idna" },
{ name = "truststore", marker = "sys_platform != 'emscripten'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7f/f8/579a8b51e42e38ee32647df9f08aa25643ae788e275cc625b199829c4671/httpx2-2.12.0.tar.gz", hash = "sha256:7631fe9887a8a2275f4a2540e053aa670fcc50742864a9ae7c66e609fdcf12cf", size = 100040, upload-time = "2026-08-18T13:22:09.086Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, { url = "https://files.pythonhosted.org/packages/c8/95/411ba65569158e862368917aaf56597f3e5fa3b91b0502919638465a08f3/httpx2-2.12.0-py3-none-any.whl", hash = "sha256:cc8b6eecb8661c146b8f89a60e97456ee086e91a784ed31ac450c3a9e613dd36", size = 95427, upload-time = "2026-08-18T13:22:06.834Z" },
]
[[package]]
name = "httpx2-jsfetch"
version = "1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872, upload-time = "2026-08-07T00:13:07.492Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" },
] ]
[[package]] [[package]]
@@ -638,11 +671,11 @@ wheels = [
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.15" version = "3.19"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" },
] ]
[[package]] [[package]]
@@ -701,15 +734,15 @@ wheels = [
[[package]] [[package]]
name = "mcp" name = "mcp"
version = "1.28.1" version = "2.1.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "anyio" }, { name = "anyio" },
{ name = "httpx" }, { name = "httpx2" },
{ name = "httpx-sse" },
{ name = "jsonschema" }, { name = "jsonschema" },
{ name = "mcp-types" },
{ name = "opentelemetry-api" },
{ name = "pydantic" }, { name = "pydantic" },
{ name = "pydantic-settings" },
{ name = "pyjwt", extra = ["crypto"] }, { name = "pyjwt", extra = ["crypto"] },
{ name = "python-multipart" }, { name = "python-multipart" },
{ name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "pywin32", marker = "sys_platform == 'win32'" },
@@ -719,9 +752,22 @@ dependencies = [
{ name = "typing-inspection" }, { name = "typing-inspection" },
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/6e/77/9450b8f251a13affb6281997d0523c4615f8a8b35d0b21ff30db3a5aac9d/mcp-1.28.1.tar.gz", hash = "sha256:d51e36a5f5644faea4f85ea649bfffa6bc6c26770d42798ad6a3de3d2ba69683", size = 638501, upload-time = "2026-06-26T12:57:29.093Z" } sdist = { url = "https://files.pythonhosted.org/packages/d4/6e/21fb8e5d579dbe21d96ea4d5034200d46d8bdf2261053b5bd041f3c2f612/mcp-2.1.1.tar.gz", hash = "sha256:50b7ba1ebbe117008ea7bdd288234043e69c20b403d6851d19661e6d431a75ef", size = 3984589, upload-time = "2026-08-25T16:14:02.376Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/e2/5e/d118fce19f87a2e7d8101c35c8ae0ec289098a4df0ff244cec23e415aca0/mcp-1.28.1-py3-none-any.whl", hash = "sha256:2726bca5e7193f61c5dde8b12500a6de2d9acf6d1a1c0be9e8c2e706437991df", size = 222620, upload-time = "2026-06-26T12:57:27.218Z" }, { url = "https://files.pythonhosted.org/packages/50/af/8644cc5fa26a59afd2df2e98eeb19e72926887fa4b7441aba4ff661140db/mcp-2.1.1-py3-none-any.whl", hash = "sha256:1c6c31c5d6471c58db76af3af8af67f46d11d01f0a59077d0a308cbdb3d3e915", size = 357912, upload-time = "2026-08-25T16:13:59.024Z" },
]
[[package]]
name = "mcp-types"
version = "2.1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/6a/dd/1c4417dc0b722c23a1669032d5f044e41170fe5d4773b488a50fcce98c32/mcp_types-2.1.1.tar.gz", hash = "sha256:77dcbe48fba73cca71a673f2646a5f037a017b7a0a07ac89cec1113028890eda", size = 66674, upload-time = "2026-08-25T16:14:03.861Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/71/d0/242e63c510f4a17381f55b1549a3f94f5687a0595984febd2b6f87a687a0/mcp_types-2.1.1-py3-none-any.whl", hash = "sha256:26f9f7f03f2a5730717a5b98e2ab7eb640ac352d05a00cdc725c311864778295", size = 69656, upload-time = "2026-08-25T16:14:00.667Z" },
] ]
[[package]] [[package]]
@@ -729,7 +775,7 @@ name = "milvus-lite"
version = "2.5.1" version = "2.5.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "tqdm", marker = "sys_platform != 'win32'" }, { name = "tqdm" },
] ]
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/a9/b2/acc5024c8e8b6a0b034670b8e8af306ebd633ede777dcbf557eac4785937/milvus_lite-2.5.1-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:6b014453200ba977be37ba660cb2d021030375fa6a35bc53c2e1d92980a0c512", size = 27934713, upload-time = "2025-06-30T04:23:37.028Z" }, { url = "https://files.pythonhosted.org/packages/a9/b2/acc5024c8e8b6a0b034670b8e8af306ebd633ede777dcbf557eac4785937/milvus_lite-2.5.1-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:6b014453200ba977be37ba660cb2d021030375fa6a35bc53c2e1d92980a0c512", size = 27934713, upload-time = "2025-06-30T04:23:37.028Z" },
@@ -812,6 +858,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" }, { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" },
] ]
[[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]] [[package]]
name = "orjson" name = "orjson"
version = "3.11.7" version = "3.11.7"
@@ -1078,20 +1136,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" },
] ]
[[package]]
name = "pydantic-settings"
version = "2.14.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
{ name = "python-dotenv" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5c/b5/8f48e906c3e0205276e8bd8cb7512217a87b2685304d64be27cad5b3019f/pydantic_settings-2.14.2.tar.gz", hash = "sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f", size = 237700, upload-time = "2026-06-19T13:44:56.324Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/77/c1/6e422f34e569cf8e18df68d1939c81c099d2b61e4f7d9621c8a77560799c/pydantic_settings-2.14.2-py3-none-any.whl", hash = "sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440", size = 61715, upload-time = "2026-06-19T13:44:55.02Z" },
]
[[package]] [[package]]
name = "pyjwt" name = "pyjwt"
version = "2.13.0" version = "2.13.0"
@@ -1403,6 +1447,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" },
] ]
[[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]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.15.0" version = "4.15.0"