Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 2 additions & 13 deletions .github/workflows/run_example_scripts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
python-version: ['3.10', '3.11', '3.12', '3.13']

steps:
- name: Checkout code
Expand Down Expand Up @@ -42,18 +42,7 @@ jobs:
touch README-PYPI.md # Create this file since the client is not built by Speakeasy
uv build

- name: For python 3.9, install the client and run examples without extra dependencies.
if: matrix.python-version == '3.9'
run: |
PACKAGE="dist/$(ls dist | grep whl | head -n 1)"
uv pip install --system "$PACKAGE"
./scripts/run_examples.sh --no-extra-dep
env:
MISTRAL_AGENT_ID: ${{ secrets.CI_AGENT_ID }}
MISTRAL_API_KEY: ${{ env.MISTRAL_API_KEY }}

- name: For python 3.10+, install client with extras and run all examples.
if: matrix.python-version != '3.9'
- name: Install client with extras and run all examples.
run: |
PACKAGE="dist/$(ls dist | grep whl | head -n 1)[agents]"
uv pip install --system "$PACKAGE"
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ It's also possible to write a standalone Python script without needing to set up
```python
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.9"
# requires-python = ">=3.10"
# dependencies = [
# "mistralai",
# ]
Expand Down Expand Up @@ -117,8 +117,7 @@ installing the package:
pip install "mistralai[agents]"
```

> Note: Because of some of our dependencies, these features are only available for python version higher or equal to
> 3.10.
> Note: These features require Python 3.10+ (the SDK minimum).

<!-- Start SDK Example Usage [usage] -->
## SDK Example Usage
Expand Down
4 changes: 2 additions & 2 deletions packages/mistralai_azure/pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ persistent=yes

# Minimum Python version to use for version dependent checks. Will default to
# the version used to run pylint.
py-version=3.9
py-version=3.10

# Discover python modules and packages in the file system subtree.
recursive=no
Expand Down Expand Up @@ -660,4 +660,4 @@ init-import=no

# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
3 changes: 1 addition & 2 deletions packages/mistralai_azure/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "mistralai_azure"
version = "1.6.0"
description = "Python Client SDK for the Mistral AI API in Azure."
authors = [{ name = "Mistral" }]
requires-python = ">=3.9.2"
requires-python = ">=3.10"
readme = "README.md"
dependencies = [
"httpcore >=1.0.9",
Expand Down Expand Up @@ -63,4 +63,3 @@ ignore_missing_imports = true
venvPath = "."
venv = ".venv"


3 changes: 1 addition & 2 deletions packages/mistralai_azure/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/mistralai_gcp/pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ persistent=yes

# Minimum Python version to use for version dependent checks. Will default to
# the version used to run pylint.
py-version=3.9
py-version=3.10

# Discover python modules and packages in the file system subtree.
recursive=no
Expand Down
3 changes: 1 addition & 2 deletions packages/mistralai_gcp/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "mistralai-gcp"
version = "1.6.0"
description = "Python Client SDK for the Mistral AI API in GCP."
authors = [{ name = "Mistral" }]
requires-python = ">=3.9"
requires-python = ">=3.10"
readme = "README-PYPI.md"
dependencies = [
"eval-type-backport >=0.2.0",
Expand Down Expand Up @@ -66,4 +66,3 @@ ignore_missing_imports = true
venvPath = "."
venv = ".venv"


8 changes: 1 addition & 7 deletions packages/mistralai_gcp/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ persistent=yes

# Minimum Python version to use for version dependent checks. Will default to
# the version used to run pylint.
py-version=3.9
py-version=3.10

# Discover python modules and packages in the file system subtree.
recursive=no
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "mistralai"
version = "1.10.0"
description = "Python Client SDK for the Mistral AI API."
authors = [{ name = "Mistral" }]
requires-python = ">=3.9"
requires-python = ">=3.10"
readme = "README-PYPI.md"
dependencies = [
"eval-type-backport >=0.2.0",
Expand All @@ -25,7 +25,7 @@ gcp = [
"requests >=2.32.3",
]
agents = [
"mcp >=1.0,<2.0; python_version >= '3.10'",
"mcp >=1.0,<2.0",
"griffe >=1.7.3,<2.0",
"authlib >=1.5.2,<2.0",
]
Expand All @@ -42,7 +42,7 @@ dev = [
"types-python-dateutil>=2.9.0.20240316,<3",
"types-authlib>=1.5.0.20250516,<2",
"types-pyyaml>=6.0.12.20250516,<7",
"mcp>=1.0,<2 ; python_version >= '3.10'",
"mcp>=1.0,<2",
"griffe>=1.7.3,<2",
"authlib>=1.5.2,<2",
]
Expand Down
21 changes: 10 additions & 11 deletions src/mistralai/extra/mcp/auth.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from typing import Optional
import logging

from authlib.oauth2.rfc8414 import AuthorizationServerMetadata
from authlib.integrations.httpx_client import AsyncOAuth2Client as AsyncOAuth2ClientBase
import httpx
import logging
from authlib.integrations.httpx_client import AsyncOAuth2Client as AsyncOAuth2ClientBase
from authlib.oauth2.rfc8414 import AuthorizationServerMetadata

from mistralai.types import BaseModel

Expand All @@ -16,8 +15,8 @@ class Oauth2AuthorizationScheme(BaseModel):
authorization_url: str
token_url: str
scope: list[str]
description: Optional[str] = None
refresh_url: Optional[str] = None
description: str | None = None
refresh_url: str | None = None


class OAuthParams(BaseModel):
Expand All @@ -42,7 +41,7 @@ def from_oauth_params(cls, oauth_params: OAuthParams) -> "AsyncOAuth2Client":

async def get_well_known_authorization_server_metadata(
server_url: str,
) -> Optional[AuthorizationServerMetadata]:
) -> AuthorizationServerMetadata | None:
"""Fetch the metadata from the well-known location.

This should be available on MCP servers as described by the specification:
Expand Down Expand Up @@ -123,10 +122,10 @@ async def dynamic_client_registration(
async def build_oauth_params(
server_url: str,
redirect_url: str,
client_id: Optional[str] = None,
client_secret: Optional[str] = None,
scope: Optional[list[str]] = None,
async_client: Optional[httpx.AsyncClient] = None,
client_id: str | None = None,
client_secret: str | None = None,
scope: list[str] | None = None,
async_client: httpx.AsyncClient | None = None,
) -> OAuthParams:
"""Get issuer metadata and build the oauth required params."""
metadata = await get_oauth_server_metadata(server_url=server_url)
Expand Down
20 changes: 10 additions & 10 deletions src/mistralai/extra/mcp/base.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from typing import Optional, Sequence, Union
import logging
import typing
from collections.abc import Sequence
from contextlib import AsyncExitStack
from typing import Protocol, Any
from typing import Any, Protocol

from mcp import ClientSession # pyright: ignore[reportMissingImports]
from mcp.types import ( # pyright: ignore[reportMissingImports]
Expand All @@ -23,16 +23,16 @@


class MCPSystemPrompt(typing.TypedDict):
description: Optional[str]
messages: list[Union[SystemMessageTypedDict, AssistantMessageTypedDict]]
description: str | None
messages: list[SystemMessageTypedDict | AssistantMessageTypedDict]


class MCPClientProtocol(Protocol):
"""MCP client that converts MCP artifacts to Mistral format."""

_name: str

async def initialize(self, exit_stack: Optional[AsyncExitStack]) -> None:
async def initialize(self, exit_stack: AsyncExitStack | None) -> None:
...

async def aclose(self) -> None:
Expand All @@ -42,7 +42,7 @@ async def get_tools(self) -> list[FunctionTool]:
...

async def execute_tool(
self, name: str, arguments: dict
self, name: str, arguments: dict[str, Any]
) -> list[TextChunkTypedDict]:
...

Expand All @@ -60,9 +60,9 @@ class MCPClientBase(MCPClientProtocol):

_session: ClientSession

def __init__(self, name: Optional[str] = None):
def __init__(self, name: str | None = None):
self._name = name or self.__class__.__name__
self._exit_stack: Optional[AsyncExitStack] = None
self._exit_stack: AsyncExitStack | None = None
self._is_initialized = False

def _convert_content(self, mcp_content: ContentBlock) -> TextChunkTypedDict:
Expand Down Expand Up @@ -109,7 +109,7 @@ async def get_system_prompt(
"description": prompt_result.description,
"messages": [
typing.cast(
Union[SystemMessageTypedDict, AssistantMessageTypedDict],
SystemMessageTypedDict | AssistantMessageTypedDict,
{
"role": message.role,
"content": self._convert_content(mcp_content=message.content),
Expand All @@ -122,7 +122,7 @@ async def get_system_prompt(
async def list_system_prompts(self) -> ListPromptsResult:
return await self._session.list_prompts()

async def initialize(self, exit_stack: Optional[AsyncExitStack] = None) -> None:
async def initialize(self, exit_stack: AsyncExitStack | None = None) -> None:
"""Initialize the MCP session."""
# client is already initialized so return
if self._is_initialized:
Expand Down
28 changes: 13 additions & 15 deletions src/mistralai/extra/mcp/sse.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import http
import logging
import typing
from typing import Any, Optional
from contextlib import AsyncExitStack
from functools import cached_property
from typing import Any

import httpx
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
from authlib.oauth2.rfc6749 import OAuth2Token
from mcp.client.sse import sse_client # pyright: ignore[reportMissingImports]
from mcp.shared.message import SessionMessage # pyright: ignore[reportMissingImports]

from mistralai.extra.exceptions import MCPAuthException
from mistralai.extra.mcp.base import (
MCPClientBase,
)
from mistralai.extra.mcp.auth import OAuthParams, AsyncOAuth2Client
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream

from mcp.client.sse import sse_client # pyright: ignore[reportMissingImports]
from mcp.shared.message import SessionMessage # pyright: ignore[reportMissingImports]
from authlib.oauth2.rfc6749 import OAuth2Token

from mistralai.types import BaseModel

Expand All @@ -27,7 +25,7 @@ class SSEServerParams(BaseModel):
"""Parameters required for a MCPClient with SSE transport"""

url: str
headers: Optional[dict[str, Any]] = None
headers: dict[str, Any] | None = None
timeout: float = 5
sse_read_timeout: float = 60 * 5

Expand All @@ -41,20 +39,20 @@ class MCPClientSSE(MCPClientBase):
This is possibly going to change in the future since the protocol has ongoing discussions.
"""

_oauth_params: Optional[OAuthParams]
_oauth_params: OAuthParams | None
_sse_params: SSEServerParams

def __init__(
self,
sse_params: SSEServerParams,
name: Optional[str] = None,
oauth_params: Optional[OAuthParams] = None,
auth_token: Optional[OAuth2Token] = None,
name: str | None = None,
oauth_params: OAuthParams | None = None,
auth_token: OAuth2Token | None = None,
):
super().__init__(name=name)
self._sse_params = sse_params
self._oauth_params: Optional[OAuthParams] = oauth_params
self._auth_token: Optional[OAuth2Token] = auth_token
self._oauth_params: OAuthParams | None = oauth_params
self._auth_token: OAuth2Token | None = auth_token

@cached_property
def base_url(self) -> str:
Expand Down Expand Up @@ -142,7 +140,7 @@ async def requires_auth(self) -> bool:
async def _get_transport(
self, exit_stack: AsyncExitStack
) -> tuple[
MemoryObjectReceiveStream[typing.Union[SessionMessage, Exception]],
MemoryObjectReceiveStream[SessionMessage | Exception],
MemoryObjectSendStream[SessionMessage],
]:
try:
Expand Down
Loading