mirror of
https://github.com/open-webui/open-webui.git
synced 2026-10-29 06:32:52 +00:00
feat: connect to MCP servers that speak protocol version 2026-07-28 (#33087)
Upgrade the MCP SDK to 2.3.0 and build MCPClient on its Client in mode='auto', which probes server/discover for the stateless 2026-07-28 protocol and falls back to the initialize handshake for legacy servers. MCPClient's public surface is unchanged. MCP_INITIALIZE_TIMEOUT becomes the session's default request timeout, so it bounds the legacy handshake, tool listing and resource reads; tool calls keep using AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER. Relates to #29778 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ea615c3a2e
commit
8151826483
6 changed files with 146 additions and 113 deletions
|
|
@ -699,9 +699,11 @@ else:
|
|||
except Exception:
|
||||
AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER = AIOHTTP_CLIENT_TIMEOUT
|
||||
|
||||
# Timeout (in seconds) for the MCP session.initialize() handshake.
|
||||
# The handshake performs a list-tools round-trip and can take tens of
|
||||
# seconds on cold-start servers or servers exposing many tools.
|
||||
# Timeout (in seconds) for each MCP protocol request other than a tool call:
|
||||
# the legacy initialize handshake, tool listing and resource reads. Raise it
|
||||
# for cold-start servers or servers exposing many tools. The SDK bounds its
|
||||
# server/discover probe separately (10s), and tool calls use
|
||||
# AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER.
|
||||
MCP_INITIALIZE_TIMEOUT = os.getenv('MCP_INITIALIZE_TIMEOUT', '10')
|
||||
try:
|
||||
MCP_INITIALIZE_TIMEOUT = int(MCP_INITIALIZE_TIMEOUT)
|
||||
|
|
|
|||
|
|
@ -1,68 +1,47 @@
|
|||
import asyncio
|
||||
import logging
|
||||
import math
|
||||
from contextlib import AsyncExitStack
|
||||
from datetime import timedelta
|
||||
from typing import Optional
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
import anyio
|
||||
import httpx
|
||||
import httpx2
|
||||
from jsonschema import Draft202012Validator, FormatChecker
|
||||
from mcp import ClientSession
|
||||
from mcp.client.auth import OAuthClientProvider, TokenStorage
|
||||
from mcp.client.streamable_http import streamablehttp_client
|
||||
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
|
||||
from mcp import Client
|
||||
from mcp.client.streamable_http import streamable_http_client
|
||||
from mcp.types import ElicitRequestFormParams, ElicitResult
|
||||
from referencing import Registry
|
||||
from open_webui.env import (
|
||||
AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL,
|
||||
AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER,
|
||||
MCP_INITIALIZE_TIMEOUT,
|
||||
)
|
||||
from open_webui.utils.json_codec import JSONCodec
|
||||
from referencing import Registry
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _build_httpx_client(headers=None, timeout=None, auth=None, verify=True):
|
||||
"""Create an httpx AsyncClient for MCP transport.
|
||||
def create_httpx_client(headers=None, auth=None):
|
||||
"""Create the httpx2 AsyncClient that carries MCP traffic.
|
||||
|
||||
Falls back to AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER when the caller
|
||||
(i.e. the MCP SDK) does not supply an explicit timeout.
|
||||
Note: verify must be passed at construction time because httpx2
|
||||
configures the SSL context during __init__. AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL
|
||||
may be True, False, or an ssl.SSLContext (when a custom CA bundle path is
|
||||
configured); verify= accepts all three.
|
||||
|
||||
Note: verify must be passed at construction time because httpx
|
||||
configures the SSL context during __init__. Setting client.verify = False
|
||||
after construction does not affect the underlying transport's SSL context.
|
||||
Redirects are not configured here: the MCP SDK follows same-origin
|
||||
redirects itself and ignores the client's follow_redirects setting.
|
||||
"""
|
||||
kwargs = {
|
||||
'follow_redirects': True,
|
||||
'verify': verify,
|
||||
}
|
||||
if timeout is not None:
|
||||
kwargs['timeout'] = timeout
|
||||
elif AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER is not None:
|
||||
kwargs['timeout'] = float(AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER)
|
||||
if headers is not None:
|
||||
kwargs['headers'] = headers
|
||||
if auth is not None:
|
||||
kwargs['auth'] = auth
|
||||
return httpx.AsyncClient(**kwargs)
|
||||
return httpx2.AsyncClient(
|
||||
headers=headers,
|
||||
auth=auth,
|
||||
verify=AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL,
|
||||
# The SDK's transport defaults: reads stay open for long-lived SSE streams.
|
||||
timeout=httpx2.Timeout(30, read=300),
|
||||
)
|
||||
|
||||
|
||||
def create_httpx_client(headers=None, timeout=None, auth=None):
|
||||
# AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL may be True, False, or an
|
||||
# ssl.SSLContext (when a custom CA bundle path is configured).
|
||||
# httpx's verify= accepts bool | str | ssl.SSLContext, so all three work.
|
||||
ssl_setting = AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL
|
||||
verify = ssl_setting if ssl_setting is not True else True
|
||||
return _build_httpx_client(headers=headers, timeout=timeout, auth=auth, verify=verify)
|
||||
|
||||
|
||||
def create_insecure_httpx_client(headers=None, timeout=None, auth=None):
|
||||
return _build_httpx_client(headers=headers, timeout=timeout, auth=auth, verify=False)
|
||||
|
||||
|
||||
class OAuthTokenAuth(httpx.Auth):
|
||||
class OAuthTokenAuth(httpx2.Auth):
|
||||
"""Resolve current credentials per request and recover from concurrent token rotation."""
|
||||
|
||||
requires_request_body = True
|
||||
|
|
@ -71,15 +50,15 @@ class OAuthTokenAuth(httpx.Auth):
|
|||
self.get_headers = get_headers
|
||||
|
||||
async def async_auth_flow(self, request):
|
||||
headers = httpx.Headers(await self.get_headers())
|
||||
headers = httpx2.Headers(await self.get_headers())
|
||||
authorization = headers.get('Authorization')
|
||||
if not authorization:
|
||||
raise httpx.RequestError('No OAuth access token available', request=request)
|
||||
raise httpx2.RequestError('No OAuth access token available', request=request)
|
||||
request.headers.update(headers)
|
||||
response = yield request
|
||||
|
||||
if response.status_code == 401:
|
||||
headers = httpx.Headers(await self.get_headers())
|
||||
headers = httpx2.Headers(await self.get_headers())
|
||||
if headers.get('Authorization') and headers['Authorization'] != authorization:
|
||||
request.headers.update(headers)
|
||||
yield request
|
||||
|
|
@ -87,7 +66,7 @@ class OAuthTokenAuth(httpx.Auth):
|
|||
|
||||
class MCPClient:
|
||||
def __init__(self, event_caller=None, server_name=''):
|
||||
self.session: Optional[ClientSession] = None
|
||||
self.client: Optional[Client] = None
|
||||
self.exit_stack = None
|
||||
self.event_caller = event_caller
|
||||
self.server_name = server_name
|
||||
|
|
@ -96,7 +75,7 @@ class MCPClient:
|
|||
async def elicit(self, context, params):
|
||||
try:
|
||||
if isinstance(params, ElicitRequestFormParams):
|
||||
Draft202012Validator.check_schema(params.requestedSchema)
|
||||
Draft202012Validator.check_schema(params.requested_schema)
|
||||
else:
|
||||
url = urlsplit(params.url)
|
||||
if url.scheme not in ('http', 'https') or not url.hostname or url.username or url.password:
|
||||
|
|
@ -106,7 +85,9 @@ class MCPClient:
|
|||
{
|
||||
'type': 'request:elicitation',
|
||||
'data': {
|
||||
**params.model_dump(mode='json', include={'mode', 'message', 'requestedSchema', 'url'}),
|
||||
**params.model_dump(
|
||||
mode='json', by_alias=True, include={'mode', 'message', 'requested_schema', 'url'}
|
||||
),
|
||||
'server_name': self.server_name,
|
||||
},
|
||||
}
|
||||
|
|
@ -119,51 +100,45 @@ class MCPClient:
|
|||
content = response.get('content')
|
||||
# Never fetch server-supplied schema references or coerce user answers.
|
||||
Draft202012Validator(
|
||||
params.requestedSchema, format_checker=FormatChecker(), registry=Registry()
|
||||
params.requested_schema, format_checker=FormatChecker(), registry=Registry()
|
||||
).validate(content)
|
||||
return ElicitResult(action=response['action'], content=content)
|
||||
except Exception as e:
|
||||
log.warning('MCP elicitation failed: %s', type(e).__name__)
|
||||
return ElicitResult(action='cancel')
|
||||
|
||||
async def connect(self, url: str, headers: Optional[dict] = None, auth: Optional[httpx.Auth] = None):
|
||||
async def connect(self, url: str, headers: Optional[dict] = None, auth: Optional[httpx2.Auth] = None):
|
||||
async with AsyncExitStack() as exit_stack:
|
||||
try:
|
||||
self._streams_context = streamablehttp_client(
|
||||
url,
|
||||
headers=headers,
|
||||
auth=auth,
|
||||
httpx_client_factory=create_httpx_client
|
||||
if AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL
|
||||
else create_insecure_httpx_client,
|
||||
http_client = await exit_stack.enter_async_context(create_httpx_client(headers=headers, auth=auth))
|
||||
|
||||
# mode='auto' (the default) probes server/discover for the stateless
|
||||
# 2026-07-28 protocol and falls back to the initialize handshake on
|
||||
# legacy servers. The read timeout bounds that handshake and every
|
||||
# later request that does not set its own; the SDK caps the probe itself.
|
||||
self.client = await exit_stack.enter_async_context(
|
||||
Client(
|
||||
streamable_http_client(url, http_client=http_client),
|
||||
read_timeout_seconds=MCP_INITIALIZE_TIMEOUT,
|
||||
elicitation_callback=self.elicit if self.event_caller else None,
|
||||
)
|
||||
)
|
||||
|
||||
transport = await exit_stack.enter_async_context(self._streams_context)
|
||||
read_stream, write_stream, _ = transport
|
||||
|
||||
self._session_context = ClientSession(
|
||||
read_stream, write_stream, elicitation_callback=self.elicit if self.event_caller else None
|
||||
)
|
||||
|
||||
self.session = await exit_stack.enter_async_context(self._session_context)
|
||||
with anyio.fail_after(MCP_INITIALIZE_TIMEOUT):
|
||||
result = await self.session.initialize()
|
||||
self.instructions = result.instructions
|
||||
self.instructions = self.client.instructions
|
||||
self.exit_stack = exit_stack.pop_all()
|
||||
except Exception as e:
|
||||
await self.disconnect()
|
||||
raise e
|
||||
|
||||
async def list_tool_specs(self) -> Optional[dict]:
|
||||
if not self.session:
|
||||
if not self.client:
|
||||
raise RuntimeError('MCP client is not connected.')
|
||||
|
||||
tools = []
|
||||
cursor = None
|
||||
while True:
|
||||
result = await self.session.list_tools(cursor=cursor)
|
||||
result = await self.client.list_tools(cursor=cursor)
|
||||
tools.extend(result.tools)
|
||||
cursor = result.nextCursor
|
||||
cursor = result.next_cursor
|
||||
if cursor is None:
|
||||
break
|
||||
|
||||
|
|
@ -172,31 +147,31 @@ class MCPClient:
|
|||
name = tool.name
|
||||
description = tool.description
|
||||
|
||||
inputSchema = tool.inputSchema
|
||||
input_schema = tool.input_schema
|
||||
|
||||
# TODO: handle outputSchema if needed
|
||||
outputSchema = getattr(tool, 'outputSchema', None)
|
||||
# TODO: handle output_schema if needed
|
||||
|
||||
tool_specs.append({'name': name, 'description': description, 'parameters': inputSchema})
|
||||
tool_specs.append({'name': name, 'description': description, 'parameters': input_schema})
|
||||
|
||||
return tool_specs
|
||||
|
||||
async def call_tool(self, function_name: str, function_args: dict) -> list[dict]:
|
||||
if not self.session:
|
||||
if not self.client:
|
||||
raise RuntimeError('MCP client is not connected.')
|
||||
|
||||
tool_call_timeout = None
|
||||
# math.inf is the SDK's own "no timeout"; None would fall back to MCP_INITIALIZE_TIMEOUT.
|
||||
tool_call_timeout = math.inf
|
||||
if AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER is not None and AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER > 0:
|
||||
tool_call_timeout = timedelta(seconds=AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER)
|
||||
tool_call_timeout = AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER
|
||||
|
||||
result = await self.session.call_tool(function_name, function_args, read_timeout_seconds=tool_call_timeout)
|
||||
result = await self.client.call_tool(function_name, function_args, read_timeout_seconds=tool_call_timeout)
|
||||
if not result:
|
||||
raise Exception('No result returned from MCP tool call.')
|
||||
|
||||
result_dict = result.model_dump(mode='json')
|
||||
result_dict = result.model_dump(mode='json', by_alias=True)
|
||||
result_content = result_dict['content']
|
||||
|
||||
if result.isError:
|
||||
if result.is_error:
|
||||
raise Exception(result_content)
|
||||
|
||||
structured_content = result_dict.get('structuredContent')
|
||||
|
|
@ -238,26 +213,26 @@ class MCPClient:
|
|||
return result_content
|
||||
|
||||
async def list_resources(self, cursor: Optional[str] = None) -> Optional[dict]:
|
||||
if not self.session:
|
||||
if not self.client:
|
||||
raise RuntimeError('MCP client is not connected.')
|
||||
|
||||
result = await self.session.list_resources(cursor=cursor)
|
||||
result = await self.client.list_resources(cursor=cursor)
|
||||
if not result:
|
||||
raise Exception('No result returned from MCP list_resources call.')
|
||||
|
||||
result_dict = result.model_dump()
|
||||
result_dict = result.model_dump(by_alias=True)
|
||||
resources = result_dict.get('resources', [])
|
||||
|
||||
return resources
|
||||
|
||||
async def read_resource(self, uri: str) -> Optional[dict]:
|
||||
if not self.session:
|
||||
if not self.client:
|
||||
raise RuntimeError('MCP client is not connected.')
|
||||
|
||||
result = await self.session.read_resource(uri)
|
||||
result = await self.client.read_resource(uri)
|
||||
if not result:
|
||||
raise Exception('No result returned from MCP read_resource call.')
|
||||
result_dict = result.model_dump()
|
||||
result_dict = result.model_dump(by_alias=True)
|
||||
|
||||
return result_dict
|
||||
|
||||
|
|
@ -273,7 +248,7 @@ class MCPClient:
|
|||
|
||||
# Prevent double-close from concurrent callers
|
||||
self.exit_stack = None
|
||||
self.session = None
|
||||
self.client = None
|
||||
|
||||
try:
|
||||
# IMPORTANT: Do NOT use asyncio.shield() or asyncio.wait_for()
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ aiofiles==25.1.0
|
|||
starlette-compress==1.7.1
|
||||
Brotli==1.2.0
|
||||
httpx[socks,http2,zstd,cli,brotli]==0.28.1
|
||||
httpx2==2.16.0
|
||||
starsessions[redis]==2.2.1
|
||||
python-mimeparse==2.0.0
|
||||
|
||||
|
|
@ -50,7 +51,7 @@ asgiref==3.11.1
|
|||
|
||||
# AI libraries
|
||||
tiktoken==0.13.0
|
||||
mcp==1.27.2
|
||||
mcp==2.3.0
|
||||
|
||||
openai==2.29.0
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ starlette-compress==1.7.1
|
|||
Brotli==1.2.0
|
||||
brotlicffi==1.2.0.1
|
||||
httpx[socks,http2,zstd,cli,brotli]==0.28.1
|
||||
httpx2==2.16.0
|
||||
starsessions[redis]==2.2.1
|
||||
python-mimeparse==2.0.0
|
||||
|
||||
|
|
@ -45,7 +46,7 @@ asgiref==3.11.1
|
|||
|
||||
# AI libraries
|
||||
tiktoken==0.13.0
|
||||
mcp==1.27.2
|
||||
mcp==2.3.0
|
||||
|
||||
openai==2.29.0
|
||||
anthropic==0.86.0
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ dependencies = [
|
|||
"Brotli==1.2.0",
|
||||
"brotlicffi==1.2.0.1",
|
||||
"httpx[socks,http2,zstd,cli,brotli]==0.28.1",
|
||||
"httpx2==2.16.0",
|
||||
"starsessions[redis]==2.2.1",
|
||||
"python-mimeparse==2.0.0",
|
||||
|
||||
|
|
@ -53,7 +54,7 @@ dependencies = [
|
|||
"tiktoken==0.13.0",
|
||||
"regex==2026.5.9",
|
||||
"google-re2==1.1.20251105",
|
||||
"mcp==1.27.2",
|
||||
"mcp==2.3.0",
|
||||
|
||||
"openai==2.29.0",
|
||||
"anthropic==0.86.0",
|
||||
|
|
|
|||
85
uv.lock
generated
85
uv.lock
generated
|
|
@ -1667,6 +1667,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.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "h11" },
|
||||
{ name = "truststore" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c0/61/afa9d6b7452f9eca73f73d45324eb0bb52f098fd5e6049bf6b8263be9b4e/httpcore2-2.16.0.tar.gz", hash = "sha256:c72cd0413563b17e1cd8354677dff887466f8ec2551679759e93126534428643", size = 69886, upload-time = "2026-10-21T07:09:45.31Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/af/c2/ce417c397afa5181f0e3c6ff01a4d01639414e1adef08730d97571297151/httpcore2-2.16.0-py3-none-any.whl", hash = "sha256:5c4358319f2602d984ad93182c9fee5160f22e9da7babdda870a5e7c4e2dc34f", size = 85070, upload-time = "2026-10-21T07:09:42.802Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httptools"
|
||||
version = "0.8.0"
|
||||
|
|
@ -1725,12 +1738,29 @@ zstd = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpx-sse"
|
||||
version = "0.4.3"
|
||||
name = "httpx2"
|
||||
version = "2.16.0"
|
||||
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 = "python_full_version >= '3.12' and sys_platform == 'emscripten'" },
|
||||
{ name = "idna" },
|
||||
{ name = "truststore", marker = "sys_platform != 'emscripten'" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/73/5ca2cedf5e4e50423d1c258877865a6b62793633b561c58499f8aeb33b7e/httpx2-2.16.0.tar.gz", hash = "sha256:0d54c98128e8f2ff7fbbde0fca8c359e5f032edeaaaac443cdb7f31f685bcd7f", size = 103116, upload-time = "2026-10-21T07:09:46.348Z" }
|
||||
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/7b/3d/2e4bc4fd1ef9770871669adb348d612a6e4786b303a23f9935b81c48dd60/httpx2-2.16.0-py3-none-any.whl", hash = "sha256:fd2c5d7909e8d86fe92d53020c310ca0e4e431b838c207c5754b9bbf0308d49a", size = 97880, upload-time = "2026-10-21T07:09:44.01Z" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
|
|
@ -2233,15 +2263,15 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "mcp"
|
||||
version = "1.27.2"
|
||||
version = "2.3.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'" },
|
||||
|
|
@ -2251,9 +2281,22 @@ dependencies = [
|
|||
{ name = "typing-inspection" },
|
||||
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/27/3c/347cf965d313f5d41764e7d46bea6ffe7d9ef13b983cc429b0340962a082/mcp-1.27.2.tar.gz", hash = "sha256:8e02db104096d1c25b28e64bde29a5c32b31bc241710213e12fd4d84985bdfef", size = 621116, upload-time = "2026-05-29T17:16:04.039Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9d/8d/e0d339616f4810e9051d4aba6887afab289ab1f81875fe908b606cdfd0e3/mcp-2.3.0.tar.gz", hash = "sha256:8b147a50441cf059dc88c684e0aeed3687f0aa0f39c6cde7b90330effd2b34d8", size = 4171955, upload-time = "2026-10-02T22:06:56.091Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/11/252c6f971dc4f16af1d98a1c469d8ba523aab00d1bb76b4d3bc1ff32eacc/mcp-1.27.2-py3-none-any.whl", hash = "sha256:d6ff5160c6ca65d93013626efb3fc249de683c30b2d8570755ceddd490344de5", size = 220498, upload-time = "2026-05-29T17:16:02.442Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/d7/4a2878f128df729db3ca04e71bfce26405cc2e0c9ebe19c592feeb4c61b9/mcp-2.3.0-py3-none-any.whl", hash = "sha256:dd0c44c089d16453e8ae31a3877a0054d7a2314caaa81f5e0541b9b1734b2377", size = 368664, upload-time = "2026-10-02T22:06:52.119Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mcp-types"
|
||||
version = "2.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9e/2d/7c251e34207f6c51000312fc8839111ac45cfe02023f90b44e7f1051dd8e/mcp_types-2.3.0.tar.gz", hash = "sha256:d1e46549edb35ee19a94940fcee6d1addd7e589ab7ea92dda83f5d84781fc362", size = 65898, upload-time = "2026-10-02T22:06:57.863Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/48/32583066b155bb5d4507e7887dc88ee23118cc4de8e2a12870c49719e2a8/mcp_types-2.3.0-py3-none-any.whl", hash = "sha256:968efdbdaedfab06adae40d378a34395f1090c5921d4be3c9cde283aaf76d91d", size = 69108, upload-time = "2026-10-02T22:06:54.099Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2793,6 +2836,7 @@ dependencies = [
|
|||
{ name = "googleapis-common-protos" },
|
||||
{ name = "hiredis" },
|
||||
{ name = "httpx", extra = ["brotli", "cli", "http2", "socks", "zstd"] },
|
||||
{ name = "httpx2" },
|
||||
{ name = "itsdangerous" },
|
||||
{ name = "joserfc" },
|
||||
{ name = "langchain-classic" },
|
||||
|
|
@ -2922,6 +2966,7 @@ requires-dist = [
|
|||
{ name = "googleapis-common-protos", specifier = "==1.75.0" },
|
||||
{ name = "hiredis", specifier = "==3.4.2" },
|
||||
{ name = "httpx", extras = ["brotli", "cli", "http2", "socks", "zstd"], specifier = "==0.28.1" },
|
||||
{ name = "httpx2", specifier = "==2.16.0" },
|
||||
{ name = "itsdangerous", specifier = "==2.2.0" },
|
||||
{ name = "joserfc", specifier = "==1.7.4" },
|
||||
{ name = "langchain-classic", specifier = "==1.0.7" },
|
||||
|
|
@ -2932,7 +2977,7 @@ requires-dist = [
|
|||
{ name = "lxml", specifier = "==6.1.1" },
|
||||
{ name = "mariadb", marker = "extra == 'mariadb'", specifier = "==1.1.14" },
|
||||
{ name = "markdown", specifier = "==3.10.2" },
|
||||
{ name = "mcp", specifier = "==1.27.2" },
|
||||
{ name = "mcp", specifier = "==2.3.0" },
|
||||
{ name = "msoffcrypto-tool", specifier = "==6.0.0" },
|
||||
{ name = "onnxruntime", specifier = "==1.26.0" },
|
||||
{ name = "openai", specifier = "==2.29.0" },
|
||||
|
|
@ -2991,7 +3036,6 @@ requires-dist = [
|
|||
{ name = "xlrd", specifier = "==2.0.2" },
|
||||
{ name = "youtube-transcript-api", specifier = "==1.2.4" },
|
||||
]
|
||||
|
||||
provides-extras = ["all", "mariadb", "postgres", "unstructured"]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
|
|
@ -3988,9 +4032,9 @@ wheels = [
|
|||
name = "pyotp"
|
||||
version = "2.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f3/b2/1d5994ba2acde054a443bd5e2d384175449c7d2b6d1a0614dbca3a63abfc/pyotp-2.9.0.tar.gz", hash = "sha256:346b6642e0dbdde3b4ff5a930b664ca82abfa116356ed48cc42c7d6590d36f63", size = 17763 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f3/b2/1d5994ba2acde054a443bd5e2d384175449c7d2b6d1a0614dbca3a63abfc/pyotp-2.9.0.tar.gz", hash = "sha256:346b6642e0dbdde3b4ff5a930b664ca82abfa116356ed48cc42c7d6590d36f63", size = 17763, upload-time = "2023-07-27T23:41:03.295Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/c0/c33c8792c3e50193ef55adb95c1c3c2786fe281123291c2dbf0eaab95a6f/pyotp-2.9.0-py3-none-any.whl", hash = "sha256:81c2e5865b8ac55e825b0358e496e1d9387c811e85bb40e71a3b29b288963612", size = 13376 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/c0/c33c8792c3e50193ef55adb95c1c3c2786fe281123291c2dbf0eaab95a6f/pyotp-2.9.0-py3-none-any.whl", hash = "sha256:81c2e5865b8ac55e825b0358e496e1d9387c811e85bb40e71a3b29b288963612", size = 13376, upload-time = "2023-07-27T23:41:01.685Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4313,9 +4357,9 @@ source = { registry = "https://pypi.org/simple" }
|
|||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8f/b2/7fc2931bfae0af02d5f53b174e9cf701adbb35f39d69c2af63d4a39f81a9/qrcode-8.2.tar.gz", hash = "sha256:35c3f2a4172b33136ab9f6b3ef1c00260dd2f66f858f24d88418a015f446506c", size = 43317 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8f/b2/7fc2931bfae0af02d5f53b174e9cf701adbb35f39d69c2af63d4a39f81a9/qrcode-8.2.tar.gz", hash = "sha256:35c3f2a4172b33136ab9f6b3ef1c00260dd2f66f858f24d88418a015f446506c", size = 43317, upload-time = "2025-05-01T15:44:24.726Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/b8/d2d6d731733f51684bbf76bf34dab3b70a9148e8f2cef2bb544fccec681a/qrcode-8.2-py3-none-any.whl", hash = "sha256:16e64e0716c14960108e85d853062c9e8bba5ca8252c0b4d0231b9df4060ff4f", size = 45986 },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/b8/d2d6d731733f51684bbf76bf34dab3b70a9148e8f2cef2bb544fccec681a/qrcode-8.2-py3-none-any.whl", hash = "sha256:16e64e0716c14960108e85d853062c9e8bba5ca8252c0b4d0231b9df4060ff4f", size = 45986, upload-time = "2025-05-01T15:44:22.781Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -5258,6 +5302,15 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/c4/6f/fb96d15db6f36d6eae4cafb998c2e0353bf59d7c4ea1662d7497f269134a/triton-3.7.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e40869937a68206ec70d7f25bb7ec6433cb083f9135e1f36dbd318dc449a728", size = 197719725, upload-time = "2026-06-17T19:53:20.419Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "truststore"
|
||||
version = "0.10.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/9f/c5201d42a484c061e528825fc8e2d565f5abd50a4ced6fb7d29c4ec99b2b/truststore-0.10.5.tar.gz", hash = "sha256:30d36967ccaded5cbb38d602c433f53600036c79d502f4533a49b60a03bbefcd", size = 28091, upload-time = "2026-10-12T22:27:31.808Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/51/e9/3a7820be2bb0fe53b6bc9c3be26d3d1158004e4c3ab953aa6840b955b1e9/truststore-0.10.5-py3-none-any.whl", hash = "sha256:9aaaedaefaf06d8b206278cf8b5012bc897f485a874503501e12d776df78951c", size = 19017, upload-time = "2026-10-12T22:27:30.377Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typer"
|
||||
version = "0.25.1"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue