Spaces:
Running
Running
Merge pull request #833 from jlowin/audio
Browse files- README.md +40 -22
- docs/servers/tools.mdx +6 -2
- examples/screenshot.py +2 -1
- src/fastmcp/__init__.py +27 -7
- src/fastmcp/client/client.py +3 -4
- src/fastmcp/client/sampling.py +5 -9
- src/fastmcp/prompts/prompt.py +3 -3
- src/fastmcp/server/auth/providers/in_memory.py +2 -2
- src/fastmcp/server/context.py +2 -2
- src/fastmcp/server/openapi.py +3 -4
- src/fastmcp/server/proxy.py +3 -7
- src/fastmcp/server/server.py +3 -7
- src/fastmcp/tools/tool.py +11 -10
- src/fastmcp/tools/tool_manager.py +3 -4
- src/fastmcp/tools/tool_transform.py +3 -5
- src/fastmcp/utilities/types.py +82 -5
- tests/client/test_streamable_http.py +6 -6
- tests/contrib/test_bulk_tool_caller.py +1 -3
- tests/server/test_auth_integration.py +3 -3
- tests/server/test_server_interactions.py +78 -9
- tests/tools/test_tool.py +52 -2
- tests/tools/test_tool_manager.py +2 -1
- tests/utilities/test_types.py +98 -0
- uv.lock +149 -147
README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
| 2 |
|
| 3 |
<!-- omit in toc -->
|
| 4 |
# FastMCP v2 🚀
|
|
|
|
| 5 |
<strong>The fast, Pythonic way to build MCP servers and clients.</strong>
|
| 6 |
|
| 7 |
*FastMCP is made with 💙 by [Prefect](https://www.prefect.io/)*
|
|
@@ -15,10 +16,11 @@
|
|
| 15 |
</div>
|
| 16 |
|
| 17 |
> [!Note]
|
|
|
|
| 18 |
> #### Beyond the Protocol
|
| 19 |
-
>
|
| 20 |
> FastMCP is the standard framework for working with the Model Context Protocol. FastMCP 1.0 was incorporated into the [official MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) in 2024.
|
| 21 |
-
>
|
| 22 |
> This is FastMCP 2.0, the **actively maintained version** that provides a complete toolkit for working with the MCP ecosystem.
|
| 23 |
>
|
| 24 |
> FastMCP 2.0 has a comprehensive set of features that go far beyond the core MCP specification, all in service of providing **the simplest path to production**. These include deployment, auth, clients, server proxying and composition, generating servers from REST APIs, dynamic tool rewriting, built-in testing tools, integrations, and more.
|
|
@@ -45,6 +47,7 @@ if __name__ == "__main__":
|
|
| 45 |
```
|
| 46 |
|
| 47 |
Run the server locally:
|
|
|
|
| 48 |
```bash
|
| 49 |
fastmcp run server.py
|
| 50 |
```
|
|
@@ -53,9 +56,10 @@ fastmcp run server.py
|
|
| 53 |
|
| 54 |
FastMCP's complete documentation is available at **[gofastmcp.com](https://gofastmcp.com)**, including detailed guides, API references, and advanced patterns. This readme provides only a high-level overview.
|
| 55 |
|
| 56 |
-
Documentation is also available in [llms.txt format](https://llmstxt.org/), which is a simple markdown standard that LLMs can consume easily.
|
| 57 |
|
| 58 |
There are two ways to access the LLM-friendly documentation:
|
|
|
|
| 59 |
- [`llms.txt`](https://gofastmcp.com/llms.txt) is essentially a sitemap, listing all the pages in the documentation.
|
| 60 |
- [`llms-full.txt`](https://gofastmcp.com/llms-full.txt) contains the entire documentation. Note this may exceed the context window of your LLM.
|
| 61 |
|
|
@@ -145,7 +149,7 @@ Learn more in the [**FastMCP Server Documentation**](https://gofastmcp.com/serve
|
|
| 145 |
|
| 146 |
### Tools
|
| 147 |
|
| 148 |
-
Tools allow LLMs to perform actions by executing your Python functions (sync or async). Ideal for computations, API calls, or side effects (like `POST`/`PUT`). FastMCP handles schema generation from type hints and docstrings. Tools can return various types, including text, JSON-serializable objects, and even images
|
| 149 |
|
| 150 |
```python
|
| 151 |
@mcp.tool
|
|
@@ -191,12 +195,13 @@ Learn more in the [**Prompts Documentation**](https://gofastmcp.com/servers/prom
|
|
| 191 |
### Context
|
| 192 |
|
| 193 |
Access MCP session capabilities within your tools, resources, or prompts by adding a `ctx: Context` parameter. Context provides methods for:
|
| 194 |
-
|
| 195 |
-
*
|
| 196 |
-
*
|
| 197 |
-
*
|
| 198 |
-
*
|
| 199 |
-
*
|
|
|
|
| 200 |
|
| 201 |
To access the context, add a parameter annotated as `Context` to any mcp-decorated function. FastMCP will automatically inject the correct context object when the function is called.
|
| 202 |
|
|
@@ -336,16 +341,19 @@ if __name__ == "__main__":
|
|
| 336 |
FastMCP supports three transport protocols:
|
| 337 |
|
| 338 |
**STDIO (Default)**: Best for local tools and command-line scripts.
|
|
|
|
| 339 |
```python
|
| 340 |
mcp.run(transport="stdio") # Default, so transport argument is optional
|
| 341 |
```
|
| 342 |
|
| 343 |
**Streamable HTTP**: Recommended for web deployments.
|
|
|
|
| 344 |
```python
|
| 345 |
mcp.run(transport="streamable-http", host="127.0.0.1", port=8000, path="/mcp")
|
| 346 |
```
|
| 347 |
|
| 348 |
**SSE**: For compatibility with existing SSE clients.
|
|
|
|
| 349 |
```python
|
| 350 |
mcp.run(transport="sse", host="127.0.0.1", port=8000)
|
| 351 |
```
|
|
@@ -358,22 +366,26 @@ Contributions are the core of open source! We welcome improvements and features.
|
|
| 358 |
|
| 359 |
### Prerequisites
|
| 360 |
|
| 361 |
-
|
| 362 |
-
|
| 363 |
|
| 364 |
### Setup
|
| 365 |
|
| 366 |
-
1. Clone the repository:
|
|
|
|
| 367 |
```bash
|
| 368 |
git clone https://github.com/jlowin/fastmcp.git
|
| 369 |
cd fastmcp
|
| 370 |
```
|
| 371 |
-
|
|
|
|
|
|
|
| 372 |
```bash
|
| 373 |
uv sync
|
| 374 |
```
|
|
|
|
| 375 |
This installs all dependencies, including dev tools.
|
| 376 |
-
|
| 377 |
3. Activate the virtual environment (e.g., `source .venv/bin/activate` or via your IDE).
|
| 378 |
|
| 379 |
### Unit Tests
|
|
@@ -381,10 +393,13 @@ Contributions are the core of open source! We welcome improvements and features.
|
|
| 381 |
FastMCP has a comprehensive unit test suite. All PRs must introduce or update tests as appropriate and pass the full suite.
|
| 382 |
|
| 383 |
Run tests using pytest:
|
|
|
|
| 384 |
```bash
|
| 385 |
pytest
|
| 386 |
```
|
|
|
|
| 387 |
or if you want an overview of the code coverage
|
|
|
|
| 388 |
```bash
|
| 389 |
uv run pytest --cov=src --cov=examples --cov-report=html
|
| 390 |
```
|
|
@@ -394,10 +409,13 @@ uv run pytest --cov=src --cov=examples --cov-report=html
|
|
| 394 |
FastMCP uses `pre-commit` for code formatting, linting, and type-checking. All PRs must pass these checks (they run automatically in CI).
|
| 395 |
|
| 396 |
Install the hooks locally:
|
|
|
|
| 397 |
```bash
|
| 398 |
uv run pre-commit install
|
| 399 |
```
|
|
|
|
| 400 |
The hooks will now run automatically on `git commit`. You can also run them manually at any time:
|
|
|
|
| 401 |
```bash
|
| 402 |
pre-commit run --all-files
|
| 403 |
# or via uv
|
|
@@ -406,11 +424,11 @@ uv run pre-commit run --all-files
|
|
| 406 |
|
| 407 |
### Pull Requests
|
| 408 |
|
| 409 |
-
1.
|
| 410 |
-
2.
|
| 411 |
-
3.
|
| 412 |
-
4.
|
| 413 |
-
5.
|
| 414 |
-
6.
|
| 415 |
|
| 416 |
-
Please open an issue or discussion for questions or suggestions before starting significant work!
|
|
|
|
| 2 |
|
| 3 |
<!-- omit in toc -->
|
| 4 |
# FastMCP v2 🚀
|
| 5 |
+
|
| 6 |
<strong>The fast, Pythonic way to build MCP servers and clients.</strong>
|
| 7 |
|
| 8 |
*FastMCP is made with 💙 by [Prefect](https://www.prefect.io/)*
|
|
|
|
| 16 |
</div>
|
| 17 |
|
| 18 |
> [!Note]
|
| 19 |
+
>
|
| 20 |
> #### Beyond the Protocol
|
| 21 |
+
>
|
| 22 |
> FastMCP is the standard framework for working with the Model Context Protocol. FastMCP 1.0 was incorporated into the [official MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) in 2024.
|
| 23 |
+
>
|
| 24 |
> This is FastMCP 2.0, the **actively maintained version** that provides a complete toolkit for working with the MCP ecosystem.
|
| 25 |
>
|
| 26 |
> FastMCP 2.0 has a comprehensive set of features that go far beyond the core MCP specification, all in service of providing **the simplest path to production**. These include deployment, auth, clients, server proxying and composition, generating servers from REST APIs, dynamic tool rewriting, built-in testing tools, integrations, and more.
|
|
|
|
| 47 |
```
|
| 48 |
|
| 49 |
Run the server locally:
|
| 50 |
+
|
| 51 |
```bash
|
| 52 |
fastmcp run server.py
|
| 53 |
```
|
|
|
|
| 56 |
|
| 57 |
FastMCP's complete documentation is available at **[gofastmcp.com](https://gofastmcp.com)**, including detailed guides, API references, and advanced patterns. This readme provides only a high-level overview.
|
| 58 |
|
| 59 |
+
Documentation is also available in [llms.txt format](https://llmstxt.org/), which is a simple markdown standard that LLMs can consume easily.
|
| 60 |
|
| 61 |
There are two ways to access the LLM-friendly documentation:
|
| 62 |
+
|
| 63 |
- [`llms.txt`](https://gofastmcp.com/llms.txt) is essentially a sitemap, listing all the pages in the documentation.
|
| 64 |
- [`llms-full.txt`](https://gofastmcp.com/llms-full.txt) contains the entire documentation. Note this may exceed the context window of your LLM.
|
| 65 |
|
|
|
|
| 149 |
|
| 150 |
### Tools
|
| 151 |
|
| 152 |
+
Tools allow LLMs to perform actions by executing your Python functions (sync or async). Ideal for computations, API calls, or side effects (like `POST`/`PUT`). FastMCP handles schema generation from type hints and docstrings. Tools can return various types, including text, JSON-serializable objects, and even images or audio aided by the FastMCP media helper classes.
|
| 153 |
|
| 154 |
```python
|
| 155 |
@mcp.tool
|
|
|
|
| 195 |
### Context
|
| 196 |
|
| 197 |
Access MCP session capabilities within your tools, resources, or prompts by adding a `ctx: Context` parameter. Context provides methods for:
|
| 198 |
+
|
| 199 |
+
- **Logging:** Log messages to MCP clients with `ctx.info()`, `ctx.error()`, etc.
|
| 200 |
+
- **LLM Sampling:** Use `ctx.sample()` to request completions from the client's LLM.
|
| 201 |
+
- **HTTP Request:** Use `ctx.http_request()` to make HTTP requests to other servers.
|
| 202 |
+
- **Resource Access:** Use `ctx.read_resource()` to access resources on the server
|
| 203 |
+
- **Progress Reporting:** Use `ctx.report_progress()` to report progress to the client.
|
| 204 |
+
- and more...
|
| 205 |
|
| 206 |
To access the context, add a parameter annotated as `Context` to any mcp-decorated function. FastMCP will automatically inject the correct context object when the function is called.
|
| 207 |
|
|
|
|
| 341 |
FastMCP supports three transport protocols:
|
| 342 |
|
| 343 |
**STDIO (Default)**: Best for local tools and command-line scripts.
|
| 344 |
+
|
| 345 |
```python
|
| 346 |
mcp.run(transport="stdio") # Default, so transport argument is optional
|
| 347 |
```
|
| 348 |
|
| 349 |
**Streamable HTTP**: Recommended for web deployments.
|
| 350 |
+
|
| 351 |
```python
|
| 352 |
mcp.run(transport="streamable-http", host="127.0.0.1", port=8000, path="/mcp")
|
| 353 |
```
|
| 354 |
|
| 355 |
**SSE**: For compatibility with existing SSE clients.
|
| 356 |
+
|
| 357 |
```python
|
| 358 |
mcp.run(transport="sse", host="127.0.0.1", port=8000)
|
| 359 |
```
|
|
|
|
| 366 |
|
| 367 |
### Prerequisites
|
| 368 |
|
| 369 |
+
- Python 3.10+
|
| 370 |
+
- [uv](https://docs.astral.sh/uv/) (Recommended for environment management)
|
| 371 |
|
| 372 |
### Setup
|
| 373 |
|
| 374 |
+
1. Clone the repository:
|
| 375 |
+
|
| 376 |
```bash
|
| 377 |
git clone https://github.com/jlowin/fastmcp.git
|
| 378 |
cd fastmcp
|
| 379 |
```
|
| 380 |
+
|
| 381 |
+
2. Create and sync the environment:
|
| 382 |
+
|
| 383 |
```bash
|
| 384 |
uv sync
|
| 385 |
```
|
| 386 |
+
|
| 387 |
This installs all dependencies, including dev tools.
|
| 388 |
+
|
| 389 |
3. Activate the virtual environment (e.g., `source .venv/bin/activate` or via your IDE).
|
| 390 |
|
| 391 |
### Unit Tests
|
|
|
|
| 393 |
FastMCP has a comprehensive unit test suite. All PRs must introduce or update tests as appropriate and pass the full suite.
|
| 394 |
|
| 395 |
Run tests using pytest:
|
| 396 |
+
|
| 397 |
```bash
|
| 398 |
pytest
|
| 399 |
```
|
| 400 |
+
|
| 401 |
or if you want an overview of the code coverage
|
| 402 |
+
|
| 403 |
```bash
|
| 404 |
uv run pytest --cov=src --cov=examples --cov-report=html
|
| 405 |
```
|
|
|
|
| 409 |
FastMCP uses `pre-commit` for code formatting, linting, and type-checking. All PRs must pass these checks (they run automatically in CI).
|
| 410 |
|
| 411 |
Install the hooks locally:
|
| 412 |
+
|
| 413 |
```bash
|
| 414 |
uv run pre-commit install
|
| 415 |
```
|
| 416 |
+
|
| 417 |
The hooks will now run automatically on `git commit`. You can also run them manually at any time:
|
| 418 |
+
|
| 419 |
```bash
|
| 420 |
pre-commit run --all-files
|
| 421 |
# or via uv
|
|
|
|
| 424 |
|
| 425 |
### Pull Requests
|
| 426 |
|
| 427 |
+
1. Fork the repository on GitHub.
|
| 428 |
+
2. Create a feature branch from `main`.
|
| 429 |
+
3. Make your changes, including tests and documentation updates.
|
| 430 |
+
4. Ensure tests and pre-commit hooks pass.
|
| 431 |
+
5. Commit your changes and push to your fork.
|
| 432 |
+
6. Open a pull request against the `main` branch of `jlowin/fastmcp`.
|
| 433 |
|
| 434 |
+
Please open an issue or discussion for questions or suggestions before starting significant work!
|
docs/servers/tools.mdx
CHANGED
|
@@ -256,7 +256,9 @@ FastMCP automatically converts the value returned by your function into the appr
|
|
| 256 |
- **`str`**: Sent as `TextContent`.
|
| 257 |
- **`dict`, `list`, Pydantic `BaseModel`**: Serialized to a JSON string and sent as `TextContent`.
|
| 258 |
- **`bytes`**: Base64 encoded and sent as `BlobResourceContents` (often within an `EmbeddedResource`).
|
| 259 |
-
- **`fastmcp.Image`**: A helper class for easily returning image data. Sent as `ImageContent`.
|
|
|
|
|
|
|
| 260 |
- **`None`**: Results in an empty response (no content is sent back to the client).
|
| 261 |
|
| 262 |
FastMCP will attempt to serialize other types to a string if possible.
|
|
@@ -266,8 +268,10 @@ At this time, FastMCP responds only to your tool's return *value*, not its retur
|
|
| 266 |
</Tip>
|
| 267 |
|
| 268 |
```python
|
| 269 |
-
from fastmcp import FastMCP
|
|
|
|
| 270 |
import io
|
|
|
|
| 271 |
try:
|
| 272 |
from PIL import Image as PILImage
|
| 273 |
except ImportError:
|
|
|
|
| 256 |
- **`str`**: Sent as `TextContent`.
|
| 257 |
- **`dict`, `list`, Pydantic `BaseModel`**: Serialized to a JSON string and sent as `TextContent`.
|
| 258 |
- **`bytes`**: Base64 encoded and sent as `BlobResourceContents` (often within an `EmbeddedResource`).
|
| 259 |
+
- **`fastmcp.utilities.types.Image`**: A helper class for easily returning image data. Sent as `ImageContent`.
|
| 260 |
+
- **`fastmcp.utilities.types.Audio`**: A helper class for easily returning audio data. Sent as `AudioContent`.
|
| 261 |
+
- **A list of any of the above**: Automatically converts each item appropriately.
|
| 262 |
- **`None`**: Results in an empty response (no content is sent back to the client).
|
| 263 |
|
| 264 |
FastMCP will attempt to serialize other types to a string if possible.
|
|
|
|
| 268 |
</Tip>
|
| 269 |
|
| 270 |
```python
|
| 271 |
+
from fastmcp import FastMCP
|
| 272 |
+
from fastmcp.utilities.types import Image
|
| 273 |
import io
|
| 274 |
+
|
| 275 |
try:
|
| 276 |
from PIL import Image as PILImage
|
| 277 |
except ImportError:
|
examples/screenshot.py
CHANGED
|
@@ -6,7 +6,8 @@ Give Claude a tool to capture and view screenshots.
|
|
| 6 |
|
| 7 |
import io
|
| 8 |
|
| 9 |
-
from fastmcp import FastMCP
|
|
|
|
| 10 |
|
| 11 |
# Create server
|
| 12 |
mcp = FastMCP("Screenshot Demo", dependencies=["pyautogui", "Pillow"])
|
|
|
|
| 6 |
|
| 7 |
import io
|
| 8 |
|
| 9 |
+
from fastmcp import FastMCP
|
| 10 |
+
from fastmcp.utilities.types import Image
|
| 11 |
|
| 12 |
# Create server
|
| 13 |
mcp = FastMCP("Screenshot Demo", dependencies=["pyautogui", "Pillow"])
|
src/fastmcp/__init__.py
CHANGED
|
@@ -11,20 +11,40 @@ from fastmcp.server.context import Context
|
|
| 11 |
import fastmcp.server
|
| 12 |
|
| 13 |
from fastmcp.client import Client
|
| 14 |
-
from fastmcp.utilities.types import Image
|
| 15 |
from . import client
|
| 16 |
|
| 17 |
__version__ = version("fastmcp")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
__all__ = [
|
| 19 |
"FastMCP",
|
| 20 |
"Context",
|
| 21 |
"client",
|
| 22 |
"Client",
|
| 23 |
"settings",
|
| 24 |
-
"Image",
|
| 25 |
]
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
# ensure deprecation warnings are displayedby default
|
| 29 |
-
if settings.deprecation_warnings:
|
| 30 |
-
warnings.simplefilter("default", DeprecationWarning)
|
|
|
|
| 11 |
import fastmcp.server
|
| 12 |
|
| 13 |
from fastmcp.client import Client
|
|
|
|
| 14 |
from . import client
|
| 15 |
|
| 16 |
__version__ = version("fastmcp")
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
# ensure deprecation warnings are displayed by default
|
| 20 |
+
if settings.deprecation_warnings:
|
| 21 |
+
warnings.simplefilter("default", DeprecationWarning)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def __getattr__(name: str):
|
| 25 |
+
"""
|
| 26 |
+
Used to deprecate the module-level Image class; can be removed once it is no longer imported to root.
|
| 27 |
+
"""
|
| 28 |
+
if name == "Image":
|
| 29 |
+
# Deprecated in 2.8.1
|
| 30 |
+
if settings.deprecation_warnings:
|
| 31 |
+
warnings.warn(
|
| 32 |
+
"The top-level `fastmcp.Image` import is deprecated "
|
| 33 |
+
"and will be removed in a future version. "
|
| 34 |
+
"Please use `fastmcp.utilities.types.Image` instead.",
|
| 35 |
+
DeprecationWarning,
|
| 36 |
+
stacklevel=2,
|
| 37 |
+
)
|
| 38 |
+
from fastmcp.utilities.types import Image
|
| 39 |
+
|
| 40 |
+
return Image
|
| 41 |
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
| 42 |
+
|
| 43 |
+
|
| 44 |
__all__ = [
|
| 45 |
"FastMCP",
|
| 46 |
"Context",
|
| 47 |
"client",
|
| 48 |
"Client",
|
| 49 |
"settings",
|
|
|
|
| 50 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/fastmcp/client/client.py
CHANGED
|
@@ -29,6 +29,7 @@ from fastmcp.exceptions import ToolError
|
|
| 29 |
from fastmcp.server import FastMCP
|
| 30 |
from fastmcp.utilities.exceptions import get_catch_handlers
|
| 31 |
from fastmcp.utilities.mcp_config import MCPConfig
|
|
|
|
| 32 |
|
| 33 |
from .transports import (
|
| 34 |
ClientTransportT,
|
|
@@ -658,9 +659,7 @@ class Client(Generic[ClientTransportT]):
|
|
| 658 |
arguments: dict[str, Any] | None = None,
|
| 659 |
timeout: datetime.timedelta | float | int | None = None,
|
| 660 |
progress_handler: ProgressHandler | None = None,
|
| 661 |
-
) -> list[
|
| 662 |
-
mcp.types.TextContent | mcp.types.ImageContent | mcp.types.EmbeddedResource
|
| 663 |
-
]:
|
| 664 |
"""Call a tool on the server.
|
| 665 |
|
| 666 |
Unlike call_tool_mcp, this method raises a ToolError if the tool call results in an error.
|
|
@@ -672,7 +671,7 @@ class Client(Generic[ClientTransportT]):
|
|
| 672 |
progress_handler (ProgressHandler | None, optional): The progress handler to use for the tool call. Defaults to None.
|
| 673 |
|
| 674 |
Returns:
|
| 675 |
-
list[mcp.types.TextContent | mcp.types.ImageContent | mcp.types.EmbeddedResource]:
|
| 676 |
The content returned by the tool.
|
| 677 |
|
| 678 |
Raises:
|
|
|
|
| 29 |
from fastmcp.server import FastMCP
|
| 30 |
from fastmcp.utilities.exceptions import get_catch_handlers
|
| 31 |
from fastmcp.utilities.mcp_config import MCPConfig
|
| 32 |
+
from fastmcp.utilities.types import MCPContent
|
| 33 |
|
| 34 |
from .transports import (
|
| 35 |
ClientTransportT,
|
|
|
|
| 659 |
arguments: dict[str, Any] | None = None,
|
| 660 |
timeout: datetime.timedelta | float | int | None = None,
|
| 661 |
progress_handler: ProgressHandler | None = None,
|
| 662 |
+
) -> list[MCPContent]:
|
|
|
|
|
|
|
| 663 |
"""Call a tool on the server.
|
| 664 |
|
| 665 |
Unlike call_tool_mcp, this method raises a ToolError if the tool call results in an error.
|
|
|
|
| 671 |
progress_handler (ProgressHandler | None, optional): The progress handler to use for the tool call. Defaults to None.
|
| 672 |
|
| 673 |
Returns:
|
| 674 |
+
list[mcp.types.TextContent | mcp.types.ImageContent | mcp.types.AudioContent | mcp.types.EmbeddedResource]:
|
| 675 |
The content returned by the tool.
|
| 676 |
|
| 677 |
Raises:
|
src/fastmcp/client/sampling.py
CHANGED
|
@@ -9,13 +9,7 @@ from mcp.shared.context import LifespanContextT, RequestContext
|
|
| 9 |
from mcp.types import CreateMessageRequestParams as SamplingParams
|
| 10 |
from mcp.types import SamplingMessage
|
| 11 |
|
| 12 |
-
__all__ = ["SamplingMessage", "SamplingParams", "
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
class MessageResult(CreateMessageResult):
|
| 16 |
-
role: mcp.types.Role = "assistant"
|
| 17 |
-
content: mcp.types.TextContent | mcp.types.ImageContent
|
| 18 |
-
model: str = "client-model"
|
| 19 |
|
| 20 |
|
| 21 |
SamplingHandler: TypeAlias = Callable[
|
|
@@ -39,8 +33,10 @@ def create_sampling_callback(sampling_handler: SamplingHandler) -> SamplingFnT:
|
|
| 39 |
result = await result
|
| 40 |
|
| 41 |
if isinstance(result, str):
|
| 42 |
-
result =
|
| 43 |
-
|
|
|
|
|
|
|
| 44 |
)
|
| 45 |
return result
|
| 46 |
except Exception as e:
|
|
|
|
| 9 |
from mcp.types import CreateMessageRequestParams as SamplingParams
|
| 10 |
from mcp.types import SamplingMessage
|
| 11 |
|
| 12 |
+
__all__ = ["SamplingMessage", "SamplingParams", "SamplingHandler"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
|
| 15 |
SamplingHandler: TypeAlias = Callable[
|
|
|
|
| 33 |
result = await result
|
| 34 |
|
| 35 |
if isinstance(result, str):
|
| 36 |
+
result = CreateMessageResult(
|
| 37 |
+
role="assistant",
|
| 38 |
+
model="fastmcp-client",
|
| 39 |
+
content=mcp.types.TextContent(type="text", text=result),
|
| 40 |
)
|
| 41 |
return result
|
| 42 |
except Exception as e:
|
src/fastmcp/prompts/prompt.py
CHANGED
|
@@ -8,9 +8,9 @@ from collections.abc import Awaitable, Callable, Sequence
|
|
| 8 |
from typing import TYPE_CHECKING, Any
|
| 9 |
|
| 10 |
import pydantic_core
|
| 11 |
-
from mcp.types import EmbeddedResource, ImageContent, PromptMessage, Role, TextContent
|
| 12 |
from mcp.types import Prompt as MCPPrompt
|
| 13 |
from mcp.types import PromptArgument as MCPPromptArgument
|
|
|
|
| 14 |
from pydantic import Field, TypeAdapter, validate_call
|
| 15 |
|
| 16 |
from fastmcp.exceptions import PromptError
|
|
@@ -20,6 +20,7 @@ from fastmcp.utilities.json_schema import compress_schema
|
|
| 20 |
from fastmcp.utilities.logging import get_logger
|
| 21 |
from fastmcp.utilities.types import (
|
| 22 |
FastMCPBaseModel,
|
|
|
|
| 23 |
find_kwarg_by_type,
|
| 24 |
get_cached_typeadapter,
|
| 25 |
)
|
|
@@ -27,13 +28,12 @@ from fastmcp.utilities.types import (
|
|
| 27 |
if TYPE_CHECKING:
|
| 28 |
pass
|
| 29 |
|
| 30 |
-
CONTENT_TYPES = TextContent | ImageContent | EmbeddedResource
|
| 31 |
|
| 32 |
logger = get_logger(__name__)
|
| 33 |
|
| 34 |
|
| 35 |
def Message(
|
| 36 |
-
content: str |
|
| 37 |
) -> PromptMessage:
|
| 38 |
"""A user-friendly constructor for PromptMessage."""
|
| 39 |
if isinstance(content, str):
|
|
|
|
| 8 |
from typing import TYPE_CHECKING, Any
|
| 9 |
|
| 10 |
import pydantic_core
|
|
|
|
| 11 |
from mcp.types import Prompt as MCPPrompt
|
| 12 |
from mcp.types import PromptArgument as MCPPromptArgument
|
| 13 |
+
from mcp.types import PromptMessage, Role, TextContent
|
| 14 |
from pydantic import Field, TypeAdapter, validate_call
|
| 15 |
|
| 16 |
from fastmcp.exceptions import PromptError
|
|
|
|
| 20 |
from fastmcp.utilities.logging import get_logger
|
| 21 |
from fastmcp.utilities.types import (
|
| 22 |
FastMCPBaseModel,
|
| 23 |
+
MCPContent,
|
| 24 |
find_kwarg_by_type,
|
| 25 |
get_cached_typeadapter,
|
| 26 |
)
|
|
|
|
| 28 |
if TYPE_CHECKING:
|
| 29 |
pass
|
| 30 |
|
|
|
|
| 31 |
|
| 32 |
logger = get_logger(__name__)
|
| 33 |
|
| 34 |
|
| 35 |
def Message(
|
| 36 |
+
content: str | MCPContent, role: Role | None = None, **kwargs: Any
|
| 37 |
) -> PromptMessage:
|
| 38 |
"""A user-friendly constructor for PromptMessage."""
|
| 39 |
if isinstance(content, str):
|
src/fastmcp/server/auth/providers/in_memory.py
CHANGED
|
@@ -184,7 +184,7 @@ class InMemoryOAuthProvider(OAuthProvider):
|
|
| 184 |
|
| 185 |
return OAuthToken(
|
| 186 |
access_token=access_token_value,
|
| 187 |
-
token_type="
|
| 188 |
expires_in=DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS,
|
| 189 |
refresh_token=refresh_token_value,
|
| 190 |
scope=" ".join(authorization_code.scopes),
|
|
@@ -254,7 +254,7 @@ class InMemoryOAuthProvider(OAuthProvider):
|
|
| 254 |
|
| 255 |
return OAuthToken(
|
| 256 |
access_token=new_access_token_value,
|
| 257 |
-
token_type="
|
| 258 |
expires_in=DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS,
|
| 259 |
refresh_token=new_refresh_token_value,
|
| 260 |
scope=" ".join(scopes),
|
|
|
|
| 184 |
|
| 185 |
return OAuthToken(
|
| 186 |
access_token=access_token_value,
|
| 187 |
+
token_type="Bearer",
|
| 188 |
expires_in=DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS,
|
| 189 |
refresh_token=refresh_token_value,
|
| 190 |
scope=" ".join(authorization_code.scopes),
|
|
|
|
| 254 |
|
| 255 |
return OAuthToken(
|
| 256 |
access_token=new_access_token_value,
|
| 257 |
+
token_type="Bearer",
|
| 258 |
expires_in=DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS,
|
| 259 |
refresh_token=new_refresh_token_value,
|
| 260 |
scope=" ".join(scopes),
|
src/fastmcp/server/context.py
CHANGED
|
@@ -11,7 +11,6 @@ from mcp.server.lowlevel.helper_types import ReadResourceContents
|
|
| 11 |
from mcp.shared.context import RequestContext
|
| 12 |
from mcp.types import (
|
| 13 |
CreateMessageResult,
|
| 14 |
-
ImageContent,
|
| 15 |
ModelHint,
|
| 16 |
ModelPreferences,
|
| 17 |
Root,
|
|
@@ -25,6 +24,7 @@ import fastmcp.server.dependencies
|
|
| 25 |
from fastmcp import settings
|
| 26 |
from fastmcp.server.server import FastMCP
|
| 27 |
from fastmcp.utilities.logging import get_logger
|
|
|
|
| 28 |
|
| 29 |
logger = get_logger(__name__)
|
| 30 |
|
|
@@ -204,7 +204,7 @@ class Context:
|
|
| 204 |
temperature: float | None = None,
|
| 205 |
max_tokens: int | None = None,
|
| 206 |
model_preferences: ModelPreferences | str | list[str] | None = None,
|
| 207 |
-
) ->
|
| 208 |
"""
|
| 209 |
Send a sampling request to the client and await the response.
|
| 210 |
|
|
|
|
| 11 |
from mcp.shared.context import RequestContext
|
| 12 |
from mcp.types import (
|
| 13 |
CreateMessageResult,
|
|
|
|
| 14 |
ModelHint,
|
| 15 |
ModelPreferences,
|
| 16 |
Root,
|
|
|
|
| 24 |
from fastmcp import settings
|
| 25 |
from fastmcp.server.server import FastMCP
|
| 26 |
from fastmcp.utilities.logging import get_logger
|
| 27 |
+
from fastmcp.utilities.types import MCPContent
|
| 28 |
|
| 29 |
logger = get_logger(__name__)
|
| 30 |
|
|
|
|
| 204 |
temperature: float | None = None,
|
| 205 |
max_tokens: int | None = None,
|
| 206 |
model_preferences: ModelPreferences | str | list[str] | None = None,
|
| 207 |
+
) -> MCPContent:
|
| 208 |
"""
|
| 209 |
Send a sampling request to the client and await the response.
|
| 210 |
|
src/fastmcp/server/openapi.py
CHANGED
|
@@ -13,7 +13,7 @@ from re import Pattern
|
|
| 13 |
from typing import TYPE_CHECKING, Any, Literal
|
| 14 |
|
| 15 |
import httpx
|
| 16 |
-
from mcp.types import
|
| 17 |
from pydantic.networks import AnyUrl
|
| 18 |
|
| 19 |
import fastmcp
|
|
@@ -29,6 +29,7 @@ from fastmcp.utilities.openapi import (
|
|
| 29 |
_combine_schemas,
|
| 30 |
format_description_with_responses,
|
| 31 |
)
|
|
|
|
| 32 |
|
| 33 |
if TYPE_CHECKING:
|
| 34 |
from fastmcp.server import Context
|
|
@@ -254,9 +255,7 @@ class OpenAPITool(Tool):
|
|
| 254 |
"""Custom representation to prevent recursion errors when printing."""
|
| 255 |
return f"OpenAPITool(name={self.name!r}, method={self._route.method}, path={self._route.path})"
|
| 256 |
|
| 257 |
-
async def run(
|
| 258 |
-
self, arguments: dict[str, Any]
|
| 259 |
-
) -> list[TextContent | ImageContent | EmbeddedResource]:
|
| 260 |
"""Execute the HTTP request based on the route configuration."""
|
| 261 |
|
| 262 |
# Prepare URL
|
|
|
|
| 13 |
from typing import TYPE_CHECKING, Any, Literal
|
| 14 |
|
| 15 |
import httpx
|
| 16 |
+
from mcp.types import ToolAnnotations
|
| 17 |
from pydantic.networks import AnyUrl
|
| 18 |
|
| 19 |
import fastmcp
|
|
|
|
| 29 |
_combine_schemas,
|
| 30 |
format_description_with_responses,
|
| 31 |
)
|
| 32 |
+
from fastmcp.utilities.types import MCPContent
|
| 33 |
|
| 34 |
if TYPE_CHECKING:
|
| 35 |
from fastmcp.server import Context
|
|
|
|
| 255 |
"""Custom representation to prevent recursion errors when printing."""
|
| 256 |
return f"OpenAPITool(name={self.name!r}, method={self._route.method}, path={self._route.path})"
|
| 257 |
|
| 258 |
+
async def run(self, arguments: dict[str, Any]) -> list[MCPContent]:
|
|
|
|
|
|
|
| 259 |
"""Execute the HTTP request based on the route configuration."""
|
| 260 |
|
| 261 |
# Prepare URL
|
src/fastmcp/server/proxy.py
CHANGED
|
@@ -9,10 +9,7 @@ from mcp.shared.exceptions import McpError
|
|
| 9 |
from mcp.types import (
|
| 10 |
METHOD_NOT_FOUND,
|
| 11 |
BlobResourceContents,
|
| 12 |
-
EmbeddedResource,
|
| 13 |
GetPromptResult,
|
| 14 |
-
ImageContent,
|
| 15 |
-
TextContent,
|
| 16 |
TextResourceContents,
|
| 17 |
)
|
| 18 |
from pydantic.networks import AnyUrl
|
|
@@ -25,6 +22,7 @@ from fastmcp.server.context import Context
|
|
| 25 |
from fastmcp.server.server import FastMCP
|
| 26 |
from fastmcp.tools.tool import Tool
|
| 27 |
from fastmcp.utilities.logging import get_logger
|
|
|
|
| 28 |
|
| 29 |
if TYPE_CHECKING:
|
| 30 |
from fastmcp.server import Context
|
|
@@ -50,7 +48,7 @@ class ProxyTool(Tool):
|
|
| 50 |
self,
|
| 51 |
arguments: dict[str, Any],
|
| 52 |
context: Context | None = None,
|
| 53 |
-
) -> list[
|
| 54 |
# the client context manager will swallow any exceptions inside a TaskGroup
|
| 55 |
# so we return the raw result and raise an exception ourselves
|
| 56 |
async with self._client:
|
|
@@ -254,9 +252,7 @@ class FastMCPProxy(FastMCP):
|
|
| 254 |
|
| 255 |
return prompts
|
| 256 |
|
| 257 |
-
async def _call_tool(
|
| 258 |
-
self, key: str, arguments: dict[str, Any]
|
| 259 |
-
) -> list[TextContent | ImageContent | EmbeddedResource]:
|
| 260 |
try:
|
| 261 |
result = await super()._call_tool(key, arguments)
|
| 262 |
return result
|
|
|
|
| 9 |
from mcp.types import (
|
| 10 |
METHOD_NOT_FOUND,
|
| 11 |
BlobResourceContents,
|
|
|
|
| 12 |
GetPromptResult,
|
|
|
|
|
|
|
| 13 |
TextResourceContents,
|
| 14 |
)
|
| 15 |
from pydantic.networks import AnyUrl
|
|
|
|
| 22 |
from fastmcp.server.server import FastMCP
|
| 23 |
from fastmcp.tools.tool import Tool
|
| 24 |
from fastmcp.utilities.logging import get_logger
|
| 25 |
+
from fastmcp.utilities.types import MCPContent
|
| 26 |
|
| 27 |
if TYPE_CHECKING:
|
| 28 |
from fastmcp.server import Context
|
|
|
|
| 48 |
self,
|
| 49 |
arguments: dict[str, Any],
|
| 50 |
context: Context | None = None,
|
| 51 |
+
) -> list[MCPContent]:
|
| 52 |
# the client context manager will swallow any exceptions inside a TaskGroup
|
| 53 |
# so we return the raw result and raise an exception ourselves
|
| 54 |
async with self._client:
|
|
|
|
| 252 |
|
| 253 |
return prompts
|
| 254 |
|
| 255 |
+
async def _call_tool(self, key: str, arguments: dict[str, Any]) -> list[MCPContent]:
|
|
|
|
|
|
|
| 256 |
try:
|
| 257 |
result = await super()._call_tool(key, arguments)
|
| 258 |
return result
|
src/fastmcp/server/server.py
CHANGED
|
@@ -25,10 +25,7 @@ from mcp.server.lowlevel.server import Server as MCPServer
|
|
| 25 |
from mcp.server.stdio import stdio_server
|
| 26 |
from mcp.types import (
|
| 27 |
AnyFunction,
|
| 28 |
-
EmbeddedResource,
|
| 29 |
GetPromptResult,
|
| 30 |
-
ImageContent,
|
| 31 |
-
TextContent,
|
| 32 |
ToolAnnotations,
|
| 33 |
)
|
| 34 |
from mcp.types import Prompt as MCPPrompt
|
|
@@ -62,6 +59,7 @@ from fastmcp.utilities.cache import TimedCache
|
|
| 62 |
from fastmcp.utilities.components import FastMCPComponent
|
| 63 |
from fastmcp.utilities.logging import get_logger
|
| 64 |
from fastmcp.utilities.mcp_config import MCPConfig
|
|
|
|
| 65 |
|
| 66 |
if TYPE_CHECKING:
|
| 67 |
from fastmcp.client import Client
|
|
@@ -514,7 +512,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 514 |
|
| 515 |
async def _mcp_call_tool(
|
| 516 |
self, key: str, arguments: dict[str, Any]
|
| 517 |
-
) -> list[
|
| 518 |
"""
|
| 519 |
Handle MCP 'callTool' requests.
|
| 520 |
|
|
@@ -540,9 +538,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 540 |
# standardize NotFound message
|
| 541 |
raise NotFoundError(f"Unknown tool: {key}")
|
| 542 |
|
| 543 |
-
async def _call_tool(
|
| 544 |
-
self, key: str, arguments: dict[str, Any]
|
| 545 |
-
) -> list[TextContent | ImageContent | EmbeddedResource]:
|
| 546 |
"""
|
| 547 |
Call a tool with raw MCP arguments. FastMCP subclasses should override
|
| 548 |
this method, not _mcp_call_tool.
|
|
|
|
| 25 |
from mcp.server.stdio import stdio_server
|
| 26 |
from mcp.types import (
|
| 27 |
AnyFunction,
|
|
|
|
| 28 |
GetPromptResult,
|
|
|
|
|
|
|
| 29 |
ToolAnnotations,
|
| 30 |
)
|
| 31 |
from mcp.types import Prompt as MCPPrompt
|
|
|
|
| 59 |
from fastmcp.utilities.components import FastMCPComponent
|
| 60 |
from fastmcp.utilities.logging import get_logger
|
| 61 |
from fastmcp.utilities.mcp_config import MCPConfig
|
| 62 |
+
from fastmcp.utilities.types import MCPContent
|
| 63 |
|
| 64 |
if TYPE_CHECKING:
|
| 65 |
from fastmcp.client import Client
|
|
|
|
| 512 |
|
| 513 |
async def _mcp_call_tool(
|
| 514 |
self, key: str, arguments: dict[str, Any]
|
| 515 |
+
) -> list[MCPContent]:
|
| 516 |
"""
|
| 517 |
Handle MCP 'callTool' requests.
|
| 518 |
|
|
|
|
| 538 |
# standardize NotFound message
|
| 539 |
raise NotFoundError(f"Unknown tool: {key}")
|
| 540 |
|
| 541 |
+
async def _call_tool(self, key: str, arguments: dict[str, Any]) -> list[MCPContent]:
|
|
|
|
|
|
|
| 542 |
"""
|
| 543 |
Call a tool with raw MCP arguments. FastMCP subclasses should override
|
| 544 |
this method, not _mcp_call_tool.
|
src/fastmcp/tools/tool.py
CHANGED
|
@@ -7,7 +7,7 @@ from dataclasses import dataclass
|
|
| 7 |
from typing import TYPE_CHECKING, Any
|
| 8 |
|
| 9 |
import pydantic_core
|
| 10 |
-
from mcp.types import
|
| 11 |
from mcp.types import Tool as MCPTool
|
| 12 |
from pydantic import Field
|
| 13 |
|
|
@@ -17,7 +17,9 @@ from fastmcp.utilities.components import FastMCPComponent
|
|
| 17 |
from fastmcp.utilities.json_schema import compress_schema
|
| 18 |
from fastmcp.utilities.logging import get_logger
|
| 19 |
from fastmcp.utilities.types import (
|
|
|
|
| 20 |
Image,
|
|
|
|
| 21 |
find_kwarg_by_type,
|
| 22 |
get_cached_typeadapter,
|
| 23 |
)
|
|
@@ -75,9 +77,7 @@ class Tool(FastMCPComponent):
|
|
| 75 |
enabled=enabled,
|
| 76 |
)
|
| 77 |
|
| 78 |
-
async def run(
|
| 79 |
-
self, arguments: dict[str, Any]
|
| 80 |
-
) -> list[TextContent | ImageContent | EmbeddedResource]:
|
| 81 |
"""Run the tool with arguments."""
|
| 82 |
raise NotImplementedError("Subclasses must implement run()")
|
| 83 |
|
|
@@ -142,9 +142,7 @@ class FunctionTool(Tool):
|
|
| 142 |
enabled=enabled if enabled is not None else True,
|
| 143 |
)
|
| 144 |
|
| 145 |
-
async def run(
|
| 146 |
-
self, arguments: dict[str, Any]
|
| 147 |
-
) -> list[TextContent | ImageContent | EmbeddedResource]:
|
| 148 |
"""Run the tool with arguments."""
|
| 149 |
from fastmcp.server.context import Context
|
| 150 |
|
|
@@ -265,17 +263,20 @@ def _convert_to_content(
|
|
| 265 |
result: Any,
|
| 266 |
serializer: Callable[[Any], str] | None = None,
|
| 267 |
_process_as_single_item: bool = False,
|
| 268 |
-
) -> list[
|
| 269 |
"""Convert a result to a sequence of content objects."""
|
| 270 |
if result is None:
|
| 271 |
return []
|
| 272 |
|
| 273 |
-
if isinstance(result,
|
| 274 |
return [result]
|
| 275 |
|
| 276 |
if isinstance(result, Image):
|
| 277 |
return [result.to_image_content()]
|
| 278 |
|
|
|
|
|
|
|
|
|
|
| 279 |
if isinstance(result, list | tuple) and not _process_as_single_item:
|
| 280 |
# if the result is a list, then it could either be a list of MCP types,
|
| 281 |
# or a "regular" list that the tool is returning, or a mix of both.
|
|
@@ -287,7 +288,7 @@ def _convert_to_content(
|
|
| 287 |
other_content = []
|
| 288 |
|
| 289 |
for item in result:
|
| 290 |
-
if isinstance(item,
|
| 291 |
mcp_types.append(_convert_to_content(item)[0])
|
| 292 |
else:
|
| 293 |
other_content.append(item)
|
|
|
|
| 7 |
from typing import TYPE_CHECKING, Any
|
| 8 |
|
| 9 |
import pydantic_core
|
| 10 |
+
from mcp.types import TextContent, ToolAnnotations
|
| 11 |
from mcp.types import Tool as MCPTool
|
| 12 |
from pydantic import Field
|
| 13 |
|
|
|
|
| 17 |
from fastmcp.utilities.json_schema import compress_schema
|
| 18 |
from fastmcp.utilities.logging import get_logger
|
| 19 |
from fastmcp.utilities.types import (
|
| 20 |
+
Audio,
|
| 21 |
Image,
|
| 22 |
+
MCPContent,
|
| 23 |
find_kwarg_by_type,
|
| 24 |
get_cached_typeadapter,
|
| 25 |
)
|
|
|
|
| 77 |
enabled=enabled,
|
| 78 |
)
|
| 79 |
|
| 80 |
+
async def run(self, arguments: dict[str, Any]) -> list[MCPContent]:
|
|
|
|
|
|
|
| 81 |
"""Run the tool with arguments."""
|
| 82 |
raise NotImplementedError("Subclasses must implement run()")
|
| 83 |
|
|
|
|
| 142 |
enabled=enabled if enabled is not None else True,
|
| 143 |
)
|
| 144 |
|
| 145 |
+
async def run(self, arguments: dict[str, Any]) -> list[MCPContent]:
|
|
|
|
|
|
|
| 146 |
"""Run the tool with arguments."""
|
| 147 |
from fastmcp.server.context import Context
|
| 148 |
|
|
|
|
| 263 |
result: Any,
|
| 264 |
serializer: Callable[[Any], str] | None = None,
|
| 265 |
_process_as_single_item: bool = False,
|
| 266 |
+
) -> list[MCPContent]:
|
| 267 |
"""Convert a result to a sequence of content objects."""
|
| 268 |
if result is None:
|
| 269 |
return []
|
| 270 |
|
| 271 |
+
if isinstance(result, MCPContent):
|
| 272 |
return [result]
|
| 273 |
|
| 274 |
if isinstance(result, Image):
|
| 275 |
return [result.to_image_content()]
|
| 276 |
|
| 277 |
+
elif isinstance(result, Audio):
|
| 278 |
+
return [result.to_audio_content()]
|
| 279 |
+
|
| 280 |
if isinstance(result, list | tuple) and not _process_as_single_item:
|
| 281 |
# if the result is a list, then it could either be a list of MCP types,
|
| 282 |
# or a "regular" list that the tool is returning, or a mix of both.
|
|
|
|
| 288 |
other_content = []
|
| 289 |
|
| 290 |
for item in result:
|
| 291 |
+
if isinstance(item, MCPContent | Image | Audio):
|
| 292 |
mcp_types.append(_convert_to_content(item)[0])
|
| 293 |
else:
|
| 294 |
other_content.append(item)
|
src/fastmcp/tools/tool_manager.py
CHANGED
|
@@ -4,13 +4,14 @@ import warnings
|
|
| 4 |
from collections.abc import Callable
|
| 5 |
from typing import TYPE_CHECKING, Any
|
| 6 |
|
| 7 |
-
from mcp.types import
|
| 8 |
|
| 9 |
from fastmcp import settings
|
| 10 |
from fastmcp.exceptions import NotFoundError, ToolError
|
| 11 |
from fastmcp.settings import DuplicateBehavior
|
| 12 |
from fastmcp.tools.tool import Tool
|
| 13 |
from fastmcp.utilities.logging import get_logger
|
|
|
|
| 14 |
|
| 15 |
if TYPE_CHECKING:
|
| 16 |
pass
|
|
@@ -120,9 +121,7 @@ class ToolManager:
|
|
| 120 |
else:
|
| 121 |
raise NotFoundError(f"Unknown tool: {key}")
|
| 122 |
|
| 123 |
-
async def call_tool(
|
| 124 |
-
self, key: str, arguments: dict[str, Any]
|
| 125 |
-
) -> list[TextContent | ImageContent | EmbeddedResource]:
|
| 126 |
"""Call a tool by name with arguments."""
|
| 127 |
tool = self.get_tool(key)
|
| 128 |
if not tool:
|
|
|
|
| 4 |
from collections.abc import Callable
|
| 5 |
from typing import TYPE_CHECKING, Any
|
| 6 |
|
| 7 |
+
from mcp.types import ToolAnnotations
|
| 8 |
|
| 9 |
from fastmcp import settings
|
| 10 |
from fastmcp.exceptions import NotFoundError, ToolError
|
| 11 |
from fastmcp.settings import DuplicateBehavior
|
| 12 |
from fastmcp.tools.tool import Tool
|
| 13 |
from fastmcp.utilities.logging import get_logger
|
| 14 |
+
from fastmcp.utilities.types import MCPContent
|
| 15 |
|
| 16 |
if TYPE_CHECKING:
|
| 17 |
pass
|
|
|
|
| 121 |
else:
|
| 122 |
raise NotFoundError(f"Unknown tool: {key}")
|
| 123 |
|
| 124 |
+
async def call_tool(self, key: str, arguments: dict[str, Any]) -> list[MCPContent]:
|
|
|
|
|
|
|
| 125 |
"""Call a tool by name with arguments."""
|
| 126 |
tool = self.get_tool(key)
|
| 127 |
if not tool:
|
src/fastmcp/tools/tool_transform.py
CHANGED
|
@@ -7,12 +7,12 @@ from dataclasses import dataclass
|
|
| 7 |
from types import EllipsisType
|
| 8 |
from typing import Any, Literal
|
| 9 |
|
| 10 |
-
from mcp.types import
|
| 11 |
from pydantic import ConfigDict
|
| 12 |
|
| 13 |
from fastmcp.tools.tool import ParsedFunction, Tool
|
| 14 |
from fastmcp.utilities.logging import get_logger
|
| 15 |
-
from fastmcp.utilities.types import get_cached_typeadapter
|
| 16 |
|
| 17 |
logger = get_logger(__name__)
|
| 18 |
|
|
@@ -202,9 +202,7 @@ class TransformedTool(Tool):
|
|
| 202 |
forwarding_fn: Callable[..., Any] # Always present, handles arg transformation
|
| 203 |
transform_args: dict[str, ArgTransform]
|
| 204 |
|
| 205 |
-
async def run(
|
| 206 |
-
self, arguments: dict[str, Any]
|
| 207 |
-
) -> list[TextContent | ImageContent | EmbeddedResource]:
|
| 208 |
"""Run the tool with context set for forward() functions.
|
| 209 |
|
| 210 |
This method executes the tool's function while setting up the context
|
|
|
|
| 7 |
from types import EllipsisType
|
| 8 |
from typing import Any, Literal
|
| 9 |
|
| 10 |
+
from mcp.types import ToolAnnotations
|
| 11 |
from pydantic import ConfigDict
|
| 12 |
|
| 13 |
from fastmcp.tools.tool import ParsedFunction, Tool
|
| 14 |
from fastmcp.utilities.logging import get_logger
|
| 15 |
+
from fastmcp.utilities.types import MCPContent, get_cached_typeadapter
|
| 16 |
|
| 17 |
logger = get_logger(__name__)
|
| 18 |
|
|
|
|
| 202 |
forwarding_fn: Callable[..., Any] # Always present, handles arg transformation
|
| 203 |
transform_args: dict[str, ArgTransform]
|
| 204 |
|
| 205 |
+
async def run(self, arguments: dict[str, Any]) -> list[MCPContent]:
|
|
|
|
|
|
|
| 206 |
"""Run the tool with context set for forward() functions.
|
| 207 |
|
| 208 |
This method executes the tool's function while setting up the context
|
src/fastmcp/utilities/types.py
CHANGED
|
@@ -6,13 +6,21 @@ from collections.abc import Callable
|
|
| 6 |
from functools import lru_cache
|
| 7 |
from pathlib import Path
|
| 8 |
from types import UnionType
|
| 9 |
-
from typing import Annotated, TypeVar, Union, get_args, get_origin
|
| 10 |
-
|
| 11 |
-
from mcp.types import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
from pydantic import BaseModel, ConfigDict, TypeAdapter
|
| 13 |
|
| 14 |
T = TypeVar("T")
|
| 15 |
|
|
|
|
|
|
|
| 16 |
|
| 17 |
class FastMCPBaseModel(BaseModel):
|
| 18 |
"""Base model for FastMCP models."""
|
|
@@ -88,6 +96,7 @@ class Image:
|
|
| 88 |
path: str | Path | None = None,
|
| 89 |
data: bytes | None = None,
|
| 90 |
format: str | None = None,
|
|
|
|
| 91 |
):
|
| 92 |
if path is None and data is None:
|
| 93 |
raise ValueError("Either path or data must be provided")
|
|
@@ -98,6 +107,7 @@ class Image:
|
|
| 98 |
self.data = data
|
| 99 |
self._format = format
|
| 100 |
self._mime_type = self._get_mime_type()
|
|
|
|
| 101 |
|
| 102 |
def _get_mime_type(self) -> str:
|
| 103 |
"""Get MIME type from format or guess from file extension."""
|
|
@@ -115,7 +125,11 @@ class Image:
|
|
| 115 |
}.get(suffix, "application/octet-stream")
|
| 116 |
return "image/png" # default for raw binary data
|
| 117 |
|
| 118 |
-
def to_image_content(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
"""Convert to MCP ImageContent."""
|
| 120 |
if self.path:
|
| 121 |
with open(self.path, "rb") as f:
|
|
@@ -125,4 +139,67 @@ class Image:
|
|
| 125 |
else:
|
| 126 |
raise ValueError("No image data available")
|
| 127 |
|
| 128 |
-
return ImageContent(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
from functools import lru_cache
|
| 7 |
from pathlib import Path
|
| 8 |
from types import UnionType
|
| 9 |
+
from typing import Annotated, TypeAlias, TypeVar, Union, get_args, get_origin
|
| 10 |
+
|
| 11 |
+
from mcp.types import (
|
| 12 |
+
Annotations,
|
| 13 |
+
AudioContent,
|
| 14 |
+
EmbeddedResource,
|
| 15 |
+
ImageContent,
|
| 16 |
+
TextContent,
|
| 17 |
+
)
|
| 18 |
from pydantic import BaseModel, ConfigDict, TypeAdapter
|
| 19 |
|
| 20 |
T = TypeVar("T")
|
| 21 |
|
| 22 |
+
MCPContent: TypeAlias = TextContent | ImageContent | AudioContent | EmbeddedResource
|
| 23 |
+
|
| 24 |
|
| 25 |
class FastMCPBaseModel(BaseModel):
|
| 26 |
"""Base model for FastMCP models."""
|
|
|
|
| 96 |
path: str | Path | None = None,
|
| 97 |
data: bytes | None = None,
|
| 98 |
format: str | None = None,
|
| 99 |
+
annotations: Annotations | None = None,
|
| 100 |
):
|
| 101 |
if path is None and data is None:
|
| 102 |
raise ValueError("Either path or data must be provided")
|
|
|
|
| 107 |
self.data = data
|
| 108 |
self._format = format
|
| 109 |
self._mime_type = self._get_mime_type()
|
| 110 |
+
self.annotations = annotations
|
| 111 |
|
| 112 |
def _get_mime_type(self) -> str:
|
| 113 |
"""Get MIME type from format or guess from file extension."""
|
|
|
|
| 125 |
}.get(suffix, "application/octet-stream")
|
| 126 |
return "image/png" # default for raw binary data
|
| 127 |
|
| 128 |
+
def to_image_content(
|
| 129 |
+
self,
|
| 130 |
+
mime_type: str | None = None,
|
| 131 |
+
annotations: Annotations | None = None,
|
| 132 |
+
) -> ImageContent:
|
| 133 |
"""Convert to MCP ImageContent."""
|
| 134 |
if self.path:
|
| 135 |
with open(self.path, "rb") as f:
|
|
|
|
| 139 |
else:
|
| 140 |
raise ValueError("No image data available")
|
| 141 |
|
| 142 |
+
return ImageContent(
|
| 143 |
+
type="image",
|
| 144 |
+
data=data,
|
| 145 |
+
mimeType=mime_type or self._mime_type,
|
| 146 |
+
annotations=annotations or self.annotations,
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
class Audio:
|
| 151 |
+
"""Helper class for returning audio from tools."""
|
| 152 |
+
|
| 153 |
+
def __init__(
|
| 154 |
+
self,
|
| 155 |
+
path: str | Path | None = None,
|
| 156 |
+
data: bytes | None = None,
|
| 157 |
+
format: str | None = None,
|
| 158 |
+
annotations: Annotations | None = None,
|
| 159 |
+
):
|
| 160 |
+
if path is None and data is None:
|
| 161 |
+
raise ValueError("Either path or data must be provided")
|
| 162 |
+
if path is not None and data is not None:
|
| 163 |
+
raise ValueError("Only one of path or data can be provided")
|
| 164 |
+
|
| 165 |
+
self.path = Path(path) if path else None
|
| 166 |
+
self.data = data
|
| 167 |
+
self._format = format
|
| 168 |
+
self._mime_type = self._get_mime_type()
|
| 169 |
+
self.annotations = annotations
|
| 170 |
+
|
| 171 |
+
def _get_mime_type(self) -> str:
|
| 172 |
+
"""Get MIME type from format or guess from file extension."""
|
| 173 |
+
if self._format:
|
| 174 |
+
return f"audio/{self._format.lower()}"
|
| 175 |
+
|
| 176 |
+
if self.path:
|
| 177 |
+
suffix = self.path.suffix.lower()
|
| 178 |
+
return {
|
| 179 |
+
".wav": "audio/wav",
|
| 180 |
+
".mp3": "audio/mpeg",
|
| 181 |
+
".ogg": "audio/ogg",
|
| 182 |
+
".m4a": "audio/mp4",
|
| 183 |
+
".flac": "audio/flac",
|
| 184 |
+
}.get(suffix, "application/octet-stream")
|
| 185 |
+
return "audio/wav" # default for raw binary data
|
| 186 |
+
|
| 187 |
+
def to_audio_content(
|
| 188 |
+
self,
|
| 189 |
+
mime_type: str | None = None,
|
| 190 |
+
annotations: Annotations | None = None,
|
| 191 |
+
) -> AudioContent:
|
| 192 |
+
if self.path:
|
| 193 |
+
with open(self.path, "rb") as f:
|
| 194 |
+
data = base64.b64encode(f.read()).decode()
|
| 195 |
+
elif self.data is not None:
|
| 196 |
+
data = base64.b64encode(self.data).decode()
|
| 197 |
+
else:
|
| 198 |
+
raise ValueError("No audio data available")
|
| 199 |
+
|
| 200 |
+
return AudioContent(
|
| 201 |
+
type="audio",
|
| 202 |
+
data=data,
|
| 203 |
+
mimeType=mime_type or self._mime_type,
|
| 204 |
+
annotations=annotations or self.annotations,
|
| 205 |
+
)
|
tests/client/test_streamable_http.py
CHANGED
|
@@ -138,16 +138,16 @@ class TestTimeout:
|
|
| 138 |
with pytest.raises(McpError, match="Timed out"):
|
| 139 |
async with Client(
|
| 140 |
transport=StreamableHttpTransport(streamable_http_server),
|
| 141 |
-
timeout=0.
|
| 142 |
) as client:
|
| 143 |
-
await client.call_tool("sleep", {"seconds": 0.
|
| 144 |
|
| 145 |
async def test_timeout_tool_call(self, streamable_http_server: str):
|
| 146 |
async with Client(
|
| 147 |
transport=StreamableHttpTransport(streamable_http_server),
|
| 148 |
) as client:
|
| 149 |
with pytest.raises(McpError):
|
| 150 |
-
await client.call_tool("sleep", {"seconds": 0.
|
| 151 |
|
| 152 |
async def test_timeout_tool_call_overrides_client_timeout(
|
| 153 |
self, streamable_http_server: str
|
|
@@ -157,7 +157,7 @@ class TestTimeout:
|
|
| 157 |
timeout=2,
|
| 158 |
) as client:
|
| 159 |
with pytest.raises(McpError):
|
| 160 |
-
await client.call_tool("sleep", {"seconds": 0.
|
| 161 |
|
| 162 |
async def test_timeout_client_timeout_overrides_tool_call_timeout_if_lower(
|
| 163 |
self, streamable_http_server: str
|
|
@@ -165,6 +165,6 @@ class TestTimeout:
|
|
| 165 |
with pytest.raises(McpError):
|
| 166 |
async with Client(
|
| 167 |
transport=StreamableHttpTransport(streamable_http_server),
|
| 168 |
-
timeout=0.
|
| 169 |
) as client:
|
| 170 |
-
await client.call_tool("sleep", {"seconds": 0.
|
|
|
|
| 138 |
with pytest.raises(McpError, match="Timed out"):
|
| 139 |
async with Client(
|
| 140 |
transport=StreamableHttpTransport(streamable_http_server),
|
| 141 |
+
timeout=0.1,
|
| 142 |
) as client:
|
| 143 |
+
await client.call_tool("sleep", {"seconds": 0.2})
|
| 144 |
|
| 145 |
async def test_timeout_tool_call(self, streamable_http_server: str):
|
| 146 |
async with Client(
|
| 147 |
transport=StreamableHttpTransport(streamable_http_server),
|
| 148 |
) as client:
|
| 149 |
with pytest.raises(McpError):
|
| 150 |
+
await client.call_tool("sleep", {"seconds": 0.2}, timeout=0.1)
|
| 151 |
|
| 152 |
async def test_timeout_tool_call_overrides_client_timeout(
|
| 153 |
self, streamable_http_server: str
|
|
|
|
| 157 |
timeout=2,
|
| 158 |
) as client:
|
| 159 |
with pytest.raises(McpError):
|
| 160 |
+
await client.call_tool("sleep", {"seconds": 0.2}, timeout=0.1)
|
| 161 |
|
| 162 |
async def test_timeout_client_timeout_overrides_tool_call_timeout_if_lower(
|
| 163 |
self, streamable_http_server: str
|
|
|
|
| 165 |
with pytest.raises(McpError):
|
| 166 |
async with Client(
|
| 167 |
transport=StreamableHttpTransport(streamable_http_server),
|
| 168 |
+
timeout=0.1,
|
| 169 |
) as client:
|
| 170 |
+
await client.call_tool("sleep", {"seconds": 0.2}, timeout=2)
|
tests/contrib/test_bulk_tool_caller.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
from typing import Any
|
| 2 |
|
| 3 |
import pytest
|
| 4 |
-
from mcp.types import
|
| 5 |
|
| 6 |
from fastmcp import FastMCP
|
| 7 |
from fastmcp.contrib.bulk_tool_caller.bulk_tool_caller import (
|
|
@@ -11,8 +11,6 @@ from fastmcp.contrib.bulk_tool_caller.bulk_tool_caller import (
|
|
| 11 |
)
|
| 12 |
from fastmcp.tools.tool import Tool
|
| 13 |
|
| 14 |
-
ContentType = TextContent | ImageContent | EmbeddedResource
|
| 15 |
-
|
| 16 |
|
| 17 |
class ToolException(Exception):
|
| 18 |
"""Custom exception for tool errors."""
|
|
|
|
| 1 |
from typing import Any
|
| 2 |
|
| 3 |
import pytest
|
| 4 |
+
from mcp.types import TextContent
|
| 5 |
|
| 6 |
from fastmcp import FastMCP
|
| 7 |
from fastmcp.contrib.bulk_tool_caller.bulk_tool_caller import (
|
|
|
|
| 11 |
)
|
| 12 |
from fastmcp.tools.tool import Tool
|
| 13 |
|
|
|
|
|
|
|
| 14 |
|
| 15 |
class ToolException(Exception):
|
| 16 |
"""Custom exception for tool errors."""
|
tests/server/test_auth_integration.py
CHANGED
|
@@ -93,7 +93,7 @@ class MockOAuthProvider(OAuthAuthorizationServerProvider):
|
|
| 93 |
|
| 94 |
return OAuthToken(
|
| 95 |
access_token=access_token,
|
| 96 |
-
token_type="
|
| 97 |
expires_in=3600,
|
| 98 |
scope="read write",
|
| 99 |
refresh_token=refresh_token,
|
|
@@ -157,7 +157,7 @@ class MockOAuthProvider(OAuthAuthorizationServerProvider):
|
|
| 157 |
|
| 158 |
return OAuthToken(
|
| 159 |
access_token=new_access_token,
|
| 160 |
-
token_type="
|
| 161 |
expires_in=3600,
|
| 162 |
scope=" ".join(scopes) if scopes else " ".join(token_info.scopes),
|
| 163 |
refresh_token=new_refresh_token,
|
|
@@ -812,7 +812,7 @@ class TestAuthEndpoints:
|
|
| 812 |
assert "token_type" in token_response
|
| 813 |
assert "refresh_token" in token_response
|
| 814 |
assert "expires_in" in token_response
|
| 815 |
-
assert token_response["token_type"] == "
|
| 816 |
|
| 817 |
# 5. Verify the access token
|
| 818 |
access_token = token_response["access_token"]
|
|
|
|
| 93 |
|
| 94 |
return OAuthToken(
|
| 95 |
access_token=access_token,
|
| 96 |
+
token_type="Bearer",
|
| 97 |
expires_in=3600,
|
| 98 |
scope="read write",
|
| 99 |
refresh_token=refresh_token,
|
|
|
|
| 157 |
|
| 158 |
return OAuthToken(
|
| 159 |
access_token=new_access_token,
|
| 160 |
+
token_type="Bearer",
|
| 161 |
expires_in=3600,
|
| 162 |
scope=" ".join(scopes) if scopes else " ".join(token_info.scopes),
|
| 163 |
refresh_token=new_refresh_token,
|
|
|
|
| 812 |
assert "token_type" in token_response
|
| 813 |
assert "refresh_token" in token_response
|
| 814 |
assert "expires_in" in token_response
|
| 815 |
+
assert token_response["token_type"] == "Bearer"
|
| 816 |
|
| 817 |
# 5. Verify the access token
|
| 818 |
access_token = token_response["access_token"]
|
tests/server/test_server_interactions.py
CHANGED
|
@@ -10,6 +10,8 @@ import pydantic_core
|
|
| 10 |
import pytest
|
| 11 |
from mcp import McpError
|
| 12 |
from mcp.types import (
|
|
|
|
|
|
|
| 13 |
ImageContent,
|
| 14 |
TextContent,
|
| 15 |
TextResourceContents,
|
|
@@ -19,11 +21,11 @@ from pydantic import AnyUrl, Field
|
|
| 19 |
from fastmcp import Client, Context, FastMCP
|
| 20 |
from fastmcp.client.transports import FastMCPTransport
|
| 21 |
from fastmcp.exceptions import ToolError
|
| 22 |
-
from fastmcp.prompts.prompt import
|
| 23 |
from fastmcp.resources import FileResource, ResourceTemplate
|
| 24 |
from fastmcp.resources.resource import FunctionResource
|
| 25 |
from fastmcp.tools.tool import Tool
|
| 26 |
-
from fastmcp.utilities.types import Image
|
| 27 |
|
| 28 |
|
| 29 |
@pytest.fixture
|
|
@@ -46,6 +48,10 @@ def tool_server():
|
|
| 46 |
def image_tool(path: str) -> Image:
|
| 47 |
return Image(path)
|
| 48 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
@mcp.tool
|
| 50 |
def mixed_content_tool() -> list[TextContent | ImageContent]:
|
| 51 |
return [
|
|
@@ -62,6 +68,15 @@ def tool_server():
|
|
| 62 |
TextContent(type="text", text="direct content"),
|
| 63 |
]
|
| 64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
return mcp
|
| 66 |
|
| 67 |
|
|
@@ -73,7 +88,7 @@ class TestTools:
|
|
| 73 |
|
| 74 |
async def test_list_tools(self, tool_server: FastMCP):
|
| 75 |
async with Client(tool_server) as client:
|
| 76 |
-
assert len(await client.list_tools()) ==
|
| 77 |
|
| 78 |
async def test_call_tool(self, tool_server: FastMCP):
|
| 79 |
async with Client(tool_server) as client:
|
|
@@ -268,6 +283,27 @@ class TestToolReturnTypes:
|
|
| 268 |
decoded = base64.b64decode(content.data)
|
| 269 |
assert decoded == b"fake png data"
|
| 270 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
async def test_tool_mixed_content(self, tool_server: FastMCP):
|
| 272 |
async with Client(tool_server) as client:
|
| 273 |
result = await client.call_tool("mixed_content_tool", {})
|
|
@@ -308,6 +344,34 @@ class TestToolReturnTypes:
|
|
| 308 |
assert isinstance(content3, TextContent)
|
| 309 |
assert content3.text == "direct content"
|
| 310 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 311 |
|
| 312 |
class TestToolParameters:
|
| 313 |
async def test_parameter_descriptions_with_field_annotations(self):
|
|
@@ -971,7 +1035,7 @@ class TestResourceTags:
|
|
| 971 |
assert {r.name for r in resources} == set()
|
| 972 |
|
| 973 |
async def test_exclude_tags_some_resources(self):
|
| 974 |
-
mcp = self.create_server(exclude_tags={"a"
|
| 975 |
|
| 976 |
async with Client(mcp) as client:
|
| 977 |
resources = await client.list_resources()
|
|
@@ -1402,6 +1466,9 @@ class TestResourceTemplatesTags:
|
|
| 1402 |
with pytest.raises(McpError, match="Unknown resource"):
|
| 1403 |
await client.read_resource("resource://1/x")
|
| 1404 |
|
|
|
|
|
|
|
|
|
|
| 1405 |
|
| 1406 |
class TestResourceTemplateContext:
|
| 1407 |
async def test_resource_template_context(self):
|
|
@@ -1495,6 +1562,9 @@ class TestResourceTemplateEnabled:
|
|
| 1495 |
templates = await client.list_resource_templates()
|
| 1496 |
assert len(templates) == 0
|
| 1497 |
|
|
|
|
|
|
|
|
|
|
| 1498 |
async def test_get_template_and_disable(self):
|
| 1499 |
mcp = FastMCP()
|
| 1500 |
|
|
@@ -1648,11 +1718,10 @@ class TestPrompts:
|
|
| 1648 |
result = await client.get_prompt("fn")
|
| 1649 |
assert result.messages[0].role == "user"
|
| 1650 |
content = result.messages[0].content
|
| 1651 |
-
assert isinstance(content, EmbeddedResource)
|
| 1652 |
-
|
| 1653 |
-
assert
|
| 1654 |
-
assert resource.
|
| 1655 |
-
assert resource.mimeType == "text/plain"
|
| 1656 |
|
| 1657 |
async def test_get_unknown_prompt(self):
|
| 1658 |
"""Test error when getting unknown prompt."""
|
|
|
|
| 10 |
import pytest
|
| 11 |
from mcp import McpError
|
| 12 |
from mcp.types import (
|
| 13 |
+
AudioContent,
|
| 14 |
+
EmbeddedResource,
|
| 15 |
ImageContent,
|
| 16 |
TextContent,
|
| 17 |
TextResourceContents,
|
|
|
|
| 21 |
from fastmcp import Client, Context, FastMCP
|
| 22 |
from fastmcp.client.transports import FastMCPTransport
|
| 23 |
from fastmcp.exceptions import ToolError
|
| 24 |
+
from fastmcp.prompts.prompt import Prompt, PromptMessage
|
| 25 |
from fastmcp.resources import FileResource, ResourceTemplate
|
| 26 |
from fastmcp.resources.resource import FunctionResource
|
| 27 |
from fastmcp.tools.tool import Tool
|
| 28 |
+
from fastmcp.utilities.types import Audio, Image
|
| 29 |
|
| 30 |
|
| 31 |
@pytest.fixture
|
|
|
|
| 48 |
def image_tool(path: str) -> Image:
|
| 49 |
return Image(path)
|
| 50 |
|
| 51 |
+
@mcp.tool
|
| 52 |
+
def audio_tool(path: str) -> Audio:
|
| 53 |
+
return Audio(path)
|
| 54 |
+
|
| 55 |
@mcp.tool
|
| 56 |
def mixed_content_tool() -> list[TextContent | ImageContent]:
|
| 57 |
return [
|
|
|
|
| 68 |
TextContent(type="text", text="direct content"),
|
| 69 |
]
|
| 70 |
|
| 71 |
+
@mcp.tool
|
| 72 |
+
def mixed_audio_list_fn(audio_path: str) -> list:
|
| 73 |
+
return [
|
| 74 |
+
"text message",
|
| 75 |
+
Audio(audio_path),
|
| 76 |
+
{"key": "value"},
|
| 77 |
+
TextContent(type="text", text="direct content"),
|
| 78 |
+
]
|
| 79 |
+
|
| 80 |
return mcp
|
| 81 |
|
| 82 |
|
|
|
|
| 88 |
|
| 89 |
async def test_list_tools(self, tool_server: FastMCP):
|
| 90 |
async with Client(tool_server) as client:
|
| 91 |
+
assert len(await client.list_tools()) == 8
|
| 92 |
|
| 93 |
async def test_call_tool(self, tool_server: FastMCP):
|
| 94 |
async with Client(tool_server) as client:
|
|
|
|
| 283 |
decoded = base64.b64decode(content.data)
|
| 284 |
assert decoded == b"fake png data"
|
| 285 |
|
| 286 |
+
async def test_audio(self, tmp_path: Path):
|
| 287 |
+
mcp = FastMCP()
|
| 288 |
+
|
| 289 |
+
@mcp.tool
|
| 290 |
+
def audio_tool(path: str) -> Audio:
|
| 291 |
+
return Audio(path)
|
| 292 |
+
|
| 293 |
+
# Create a test audio file
|
| 294 |
+
audio_path = tmp_path / "test.wav"
|
| 295 |
+
audio_path.write_bytes(b"fake wav data")
|
| 296 |
+
|
| 297 |
+
async with Client(mcp) as client:
|
| 298 |
+
result = await client.call_tool("audio_tool", {"path": str(audio_path)})
|
| 299 |
+
content = result[0]
|
| 300 |
+
assert isinstance(content, AudioContent)
|
| 301 |
+
assert content.type == "audio"
|
| 302 |
+
assert content.mimeType == "audio/wav"
|
| 303 |
+
# Verify base64 encoding
|
| 304 |
+
decoded = base64.b64decode(content.data)
|
| 305 |
+
assert decoded == b"fake wav data"
|
| 306 |
+
|
| 307 |
async def test_tool_mixed_content(self, tool_server: FastMCP):
|
| 308 |
async with Client(tool_server) as client:
|
| 309 |
result = await client.call_tool("mixed_content_tool", {})
|
|
|
|
| 344 |
assert isinstance(content3, TextContent)
|
| 345 |
assert content3.text == "direct content"
|
| 346 |
|
| 347 |
+
async def test_tool_mixed_list_with_audio(
|
| 348 |
+
self, tool_server: FastMCP, tmp_path: Path
|
| 349 |
+
):
|
| 350 |
+
"""Test that lists containing Audio objects and other types are handled
|
| 351 |
+
correctly. Note that the non-MCP content will be grouped together."""
|
| 352 |
+
# Create a test audio file
|
| 353 |
+
audio_path = tmp_path / "test.wav"
|
| 354 |
+
audio_path.write_bytes(b"test audio data")
|
| 355 |
+
|
| 356 |
+
async with Client(tool_server) as client:
|
| 357 |
+
result = await client.call_tool(
|
| 358 |
+
"mixed_audio_list_fn", {"audio_path": str(audio_path)}
|
| 359 |
+
)
|
| 360 |
+
assert len(result) == 3
|
| 361 |
+
# Check text conversion
|
| 362 |
+
content1 = result[0]
|
| 363 |
+
assert isinstance(content1, TextContent)
|
| 364 |
+
assert json.loads(content1.text) == ["text message", {"key": "value"}]
|
| 365 |
+
# Check audio conversion
|
| 366 |
+
content2 = result[1]
|
| 367 |
+
assert isinstance(content2, AudioContent)
|
| 368 |
+
assert content2.mimeType == "audio/wav"
|
| 369 |
+
assert base64.b64decode(content2.data) == b"test audio data"
|
| 370 |
+
# Check direct TextContent
|
| 371 |
+
content3 = result[2]
|
| 372 |
+
assert isinstance(content3, TextContent)
|
| 373 |
+
assert content3.text == "direct content"
|
| 374 |
+
|
| 375 |
|
| 376 |
class TestToolParameters:
|
| 377 |
async def test_parameter_descriptions_with_field_annotations(self):
|
|
|
|
| 1035 |
assert {r.name for r in resources} == set()
|
| 1036 |
|
| 1037 |
async def test_exclude_tags_some_resources(self):
|
| 1038 |
+
mcp = self.create_server(exclude_tags={"a"})
|
| 1039 |
|
| 1040 |
async with Client(mcp) as client:
|
| 1041 |
resources = await client.list_resources()
|
|
|
|
| 1466 |
with pytest.raises(McpError, match="Unknown resource"):
|
| 1467 |
await client.read_resource("resource://1/x")
|
| 1468 |
|
| 1469 |
+
result = await client.read_resource("resource://2/x")
|
| 1470 |
+
assert result[0].text == "Template resource 2: x" # type: ignore[attr-defined]
|
| 1471 |
+
|
| 1472 |
|
| 1473 |
class TestResourceTemplateContext:
|
| 1474 |
async def test_resource_template_context(self):
|
|
|
|
| 1562 |
templates = await client.list_resource_templates()
|
| 1563 |
assert len(templates) == 0
|
| 1564 |
|
| 1565 |
+
with pytest.raises(McpError, match="Unknown resource"):
|
| 1566 |
+
await client.read_resource(AnyUrl("resource://test"))
|
| 1567 |
+
|
| 1568 |
async def test_get_template_and_disable(self):
|
| 1569 |
mcp = FastMCP()
|
| 1570 |
|
|
|
|
| 1718 |
result = await client.get_prompt("fn")
|
| 1719 |
assert result.messages[0].role == "user"
|
| 1720 |
content = result.messages[0].content
|
| 1721 |
+
assert isinstance(content, EmbeddedResource)
|
| 1722 |
+
assert isinstance(content.resource, TextResourceContents)
|
| 1723 |
+
assert content.resource.text == "File contents"
|
| 1724 |
+
assert content.resource.mimeType == "text/plain"
|
|
|
|
| 1725 |
|
| 1726 |
async def test_get_unknown_prompt(self):
|
| 1727 |
"""Test error when getting unknown prompt."""
|
tests/tools/test_tool.py
CHANGED
|
@@ -1,12 +1,19 @@
|
|
| 1 |
import pytest
|
| 2 |
-
from mcp.types import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
from pydantic import AnyUrl, BaseModel
|
| 4 |
|
| 5 |
-
from fastmcp import FastMCP
|
| 6 |
from fastmcp.client import Client
|
| 7 |
from fastmcp.exceptions import ToolError
|
| 8 |
from fastmcp.tools.tool import Tool, _convert_to_content
|
| 9 |
from fastmcp.utilities.tests import temporary_settings
|
|
|
|
| 10 |
|
| 11 |
|
| 12 |
class TestToolFromFunction:
|
|
@@ -97,6 +104,16 @@ class TestToolFromFunction:
|
|
| 97 |
assert tool.parameters["properties"]["data"]["type"] == "string"
|
| 98 |
assert isinstance(result[0], ImageContent)
|
| 99 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
def test_non_callable_fn(self):
|
| 101 |
with pytest.raises(TypeError, match="not a callable object"):
|
| 102 |
Tool.from_function(1) # type: ignore
|
|
@@ -440,6 +457,17 @@ class TestConvertResultToContent:
|
|
| 440 |
assert isinstance(result[0], ImageContent)
|
| 441 |
assert result[0].data == "ZmFrZWltYWdlZGF0YQ=="
|
| 442 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 443 |
def test_basic_type_result(self):
|
| 444 |
"""Test that a basic type is converted to TextContent."""
|
| 445 |
result = _convert_to_content(123)
|
|
@@ -524,6 +552,28 @@ class TestConvertResultToContent:
|
|
| 524 |
image_item = next(item for item in result if isinstance(item, ImageContent))
|
| 525 |
assert image_item.data == "ZmFrZWltYWdlZGF0YQ=="
|
| 526 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 527 |
def test_empty_list(self):
|
| 528 |
"""Test that an empty list results in an empty list."""
|
| 529 |
result = _convert_to_content([])
|
|
|
|
| 1 |
import pytest
|
| 2 |
+
from mcp.types import (
|
| 3 |
+
AudioContent,
|
| 4 |
+
EmbeddedResource,
|
| 5 |
+
ImageContent,
|
| 6 |
+
TextContent,
|
| 7 |
+
TextResourceContents,
|
| 8 |
+
)
|
| 9 |
from pydantic import AnyUrl, BaseModel
|
| 10 |
|
| 11 |
+
from fastmcp import FastMCP
|
| 12 |
from fastmcp.client import Client
|
| 13 |
from fastmcp.exceptions import ToolError
|
| 14 |
from fastmcp.tools.tool import Tool, _convert_to_content
|
| 15 |
from fastmcp.utilities.tests import temporary_settings
|
| 16 |
+
from fastmcp.utilities.types import Audio, Image
|
| 17 |
|
| 18 |
|
| 19 |
class TestToolFromFunction:
|
|
|
|
| 104 |
assert tool.parameters["properties"]["data"]["type"] == "string"
|
| 105 |
assert isinstance(result[0], ImageContent)
|
| 106 |
|
| 107 |
+
async def test_tool_with_audio_return(self):
|
| 108 |
+
def audio_tool(data: bytes) -> Audio:
|
| 109 |
+
return Audio(data=data)
|
| 110 |
+
|
| 111 |
+
tool = Tool.from_function(audio_tool)
|
| 112 |
+
|
| 113 |
+
result = await tool.run({"data": "test.wav"})
|
| 114 |
+
assert tool.parameters["properties"]["data"]["type"] == "string"
|
| 115 |
+
assert isinstance(result[0], AudioContent)
|
| 116 |
+
|
| 117 |
def test_non_callable_fn(self):
|
| 118 |
with pytest.raises(TypeError, match="not a callable object"):
|
| 119 |
Tool.from_function(1) # type: ignore
|
|
|
|
| 457 |
assert isinstance(result[0], ImageContent)
|
| 458 |
assert result[0].data == "ZmFrZWltYWdlZGF0YQ=="
|
| 459 |
|
| 460 |
+
def test_audio_object_result(self):
|
| 461 |
+
"""Test that an Audio object is converted to AudioContent."""
|
| 462 |
+
audio_obj = Audio(data=b"fakeaudiodata")
|
| 463 |
+
|
| 464 |
+
result = _convert_to_content(audio_obj)
|
| 465 |
+
|
| 466 |
+
assert isinstance(result, list)
|
| 467 |
+
assert len(result) == 1
|
| 468 |
+
assert isinstance(result[0], AudioContent)
|
| 469 |
+
assert result[0].data == "ZmFrZWF1ZGlvZGF0YQ=="
|
| 470 |
+
|
| 471 |
def test_basic_type_result(self):
|
| 472 |
"""Test that a basic type is converted to TextContent."""
|
| 473 |
result = _convert_to_content(123)
|
|
|
|
| 552 |
image_item = next(item for item in result if isinstance(item, ImageContent))
|
| 553 |
assert image_item.data == "ZmFrZWltYWdlZGF0YQ=="
|
| 554 |
|
| 555 |
+
def test_list_of_mixed_types_with_audio(self):
|
| 556 |
+
"""Test that a list of mixed types including Audio is converted correctly."""
|
| 557 |
+
content1 = TextContent(type="text", text="hello")
|
| 558 |
+
audio_obj = Audio(data=b"fakeaudiodata")
|
| 559 |
+
basic_data = {"a": 1}
|
| 560 |
+
result = _convert_to_content([content1, audio_obj, basic_data])
|
| 561 |
+
|
| 562 |
+
assert isinstance(result, list)
|
| 563 |
+
assert len(result) == 3
|
| 564 |
+
|
| 565 |
+
text_content_count = sum(isinstance(item, TextContent) for item in result)
|
| 566 |
+
audio_content_count = sum(isinstance(item, AudioContent) for item in result)
|
| 567 |
+
|
| 568 |
+
assert text_content_count == 2
|
| 569 |
+
assert audio_content_count == 1
|
| 570 |
+
|
| 571 |
+
text_item = next(item for item in result if isinstance(item, TextContent))
|
| 572 |
+
assert text_item.text == '{\n "a": 1\n}'
|
| 573 |
+
|
| 574 |
+
audio_item = next(item for item in result if isinstance(item, AudioContent))
|
| 575 |
+
assert audio_item.data == "ZmFrZWF1ZGlvZGF0YQ=="
|
| 576 |
+
|
| 577 |
def test_empty_list(self):
|
| 578 |
"""Test that an empty list results in an empty list."""
|
| 579 |
result = _convert_to_content([])
|
tests/tools/test_tool_manager.py
CHANGED
|
@@ -8,11 +8,12 @@ import pytest
|
|
| 8 |
from mcp.types import ImageContent
|
| 9 |
from pydantic import BaseModel
|
| 10 |
|
| 11 |
-
from fastmcp import Context, FastMCP
|
| 12 |
from fastmcp.exceptions import NotFoundError, ToolError
|
| 13 |
from fastmcp.tools import FunctionTool, ToolManager
|
| 14 |
from fastmcp.tools.tool import Tool
|
| 15 |
from fastmcp.utilities.tests import temporary_settings
|
|
|
|
| 16 |
|
| 17 |
|
| 18 |
class TestAddTools:
|
|
|
|
| 8 |
from mcp.types import ImageContent
|
| 9 |
from pydantic import BaseModel
|
| 10 |
|
| 11 |
+
from fastmcp import Context, FastMCP
|
| 12 |
from fastmcp.exceptions import NotFoundError, ToolError
|
| 13 |
from fastmcp.tools import FunctionTool, ToolManager
|
| 14 |
from fastmcp.tools.tool import Tool
|
| 15 |
from fastmcp.utilities.tests import temporary_settings
|
| 16 |
+
from fastmcp.utilities.types import Image
|
| 17 |
|
| 18 |
|
| 19 |
class TestAddTools:
|
tests/utilities/test_types.py
CHANGED
|
@@ -5,6 +5,7 @@ from typing import Annotated, Any
|
|
| 5 |
import pytest
|
| 6 |
|
| 7 |
from fastmcp.utilities.types import (
|
|
|
|
| 8 |
Image,
|
| 9 |
find_kwarg_by_type,
|
| 10 |
is_class_member_of_type,
|
|
@@ -202,6 +203,103 @@ class TestImage:
|
|
| 202 |
img.to_image_content()
|
| 203 |
|
| 204 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
class TestFindKwargByType:
|
| 206 |
def test_exact_type_match(self):
|
| 207 |
"""Test finding parameter with exact type match."""
|
|
|
|
| 5 |
import pytest
|
| 6 |
|
| 7 |
from fastmcp.utilities.types import (
|
| 8 |
+
Audio,
|
| 9 |
Image,
|
| 10 |
find_kwarg_by_type,
|
| 11 |
is_class_member_of_type,
|
|
|
|
| 203 |
img.to_image_content()
|
| 204 |
|
| 205 |
|
| 206 |
+
class TestAudio:
|
| 207 |
+
def test_audio_initialization_with_path(self):
|
| 208 |
+
"""Test audio initialization with a path."""
|
| 209 |
+
# Mock test - we're not actually going to read a file
|
| 210 |
+
audio = Audio(path="test.wav")
|
| 211 |
+
assert audio.path is not None
|
| 212 |
+
assert audio.data is None
|
| 213 |
+
assert audio._mime_type == "audio/wav"
|
| 214 |
+
|
| 215 |
+
def test_audio_initialization_with_data(self):
|
| 216 |
+
"""Test audio initialization with data."""
|
| 217 |
+
audio = Audio(data=b"test")
|
| 218 |
+
assert audio.path is None
|
| 219 |
+
assert audio.data == b"test"
|
| 220 |
+
assert audio._mime_type == "audio/wav" # Default for raw data
|
| 221 |
+
|
| 222 |
+
def test_audio_initialization_with_format(self):
|
| 223 |
+
"""Test audio initialization with a specific format."""
|
| 224 |
+
audio = Audio(data=b"test", format="mp3")
|
| 225 |
+
assert audio._mime_type == "audio/mp3"
|
| 226 |
+
|
| 227 |
+
def test_missing_data_and_path_raises_error(self):
|
| 228 |
+
"""Test that error is raised when neither path nor data is provided."""
|
| 229 |
+
with pytest.raises(ValueError, match="Either path or data must be provided"):
|
| 230 |
+
Audio()
|
| 231 |
+
|
| 232 |
+
def test_both_data_and_path_raises_error(self):
|
| 233 |
+
"""Test that error is raised when both path and data are provided."""
|
| 234 |
+
with pytest.raises(
|
| 235 |
+
ValueError, match="Only one of path or data can be provided"
|
| 236 |
+
):
|
| 237 |
+
Audio(path="test.wav", data=b"test")
|
| 238 |
+
|
| 239 |
+
def test_get_mime_type_from_path(self, tmp_path):
|
| 240 |
+
"""Test MIME type detection from file extension."""
|
| 241 |
+
extensions = {
|
| 242 |
+
".wav": "audio/wav",
|
| 243 |
+
".mp3": "audio/mpeg",
|
| 244 |
+
".ogg": "audio/ogg",
|
| 245 |
+
".m4a": "audio/mp4",
|
| 246 |
+
".flac": "audio/flac",
|
| 247 |
+
".unknown": "application/octet-stream",
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
for ext, mime in extensions.items():
|
| 251 |
+
path = tmp_path / f"test{ext}"
|
| 252 |
+
path.write_bytes(b"fake audio data")
|
| 253 |
+
audio = Audio(path=path)
|
| 254 |
+
assert audio._mime_type == mime
|
| 255 |
+
|
| 256 |
+
def test_to_audio_content(self, tmp_path, monkeypatch):
|
| 257 |
+
"""Test conversion to AudioContent."""
|
| 258 |
+
# Test with path
|
| 259 |
+
audio_path = tmp_path / "test.wav"
|
| 260 |
+
test_data = b"fake audio data"
|
| 261 |
+
audio_path.write_bytes(test_data)
|
| 262 |
+
|
| 263 |
+
audio = Audio(path=audio_path)
|
| 264 |
+
content = audio.to_audio_content()
|
| 265 |
+
|
| 266 |
+
assert content.type == "audio"
|
| 267 |
+
assert content.mimeType == "audio/wav"
|
| 268 |
+
assert content.data == base64.b64encode(test_data).decode()
|
| 269 |
+
|
| 270 |
+
# Test with data
|
| 271 |
+
audio = Audio(data=test_data, format="mp3")
|
| 272 |
+
content = audio.to_audio_content()
|
| 273 |
+
|
| 274 |
+
assert content.type == "audio"
|
| 275 |
+
assert content.mimeType == "audio/mp3"
|
| 276 |
+
assert content.data == base64.b64encode(test_data).decode()
|
| 277 |
+
|
| 278 |
+
def test_to_audio_content_error(self, monkeypatch):
|
| 279 |
+
"""Test error case in to_audio_content."""
|
| 280 |
+
# Create an Audio with neither path nor data (shouldn't happen due to __init__ checks,
|
| 281 |
+
# but testing the method's own error handling)
|
| 282 |
+
audio = Audio(data=b"test")
|
| 283 |
+
monkeypatch.setattr(audio, "path", None)
|
| 284 |
+
monkeypatch.setattr(audio, "data", None)
|
| 285 |
+
|
| 286 |
+
with pytest.raises(ValueError, match="No audio data available"):
|
| 287 |
+
audio.to_audio_content()
|
| 288 |
+
|
| 289 |
+
def test_to_audio_content_with_override_mime_type(self, tmp_path):
|
| 290 |
+
"""Test conversion to AudioContent with override MIME type."""
|
| 291 |
+
audio_path = tmp_path / "test.wav"
|
| 292 |
+
test_data = b"fake audio data"
|
| 293 |
+
audio_path.write_bytes(test_data)
|
| 294 |
+
|
| 295 |
+
audio = Audio(path=audio_path)
|
| 296 |
+
content = audio.to_audio_content(mime_type="audio/custom")
|
| 297 |
+
|
| 298 |
+
assert content.type == "audio"
|
| 299 |
+
assert content.mimeType == "audio/custom"
|
| 300 |
+
assert content.data == base64.b64encode(test_data).decode()
|
| 301 |
+
|
| 302 |
+
|
| 303 |
class TestFindKwargByType:
|
| 304 |
def test_exact_type_match(self):
|
| 305 |
"""Test finding parameter with exact type match."""
|
uv.lock
CHANGED
|
@@ -210,7 +210,7 @@ wheels = [
|
|
| 210 |
|
| 211 |
[[package]]
|
| 212 |
name = "copychat"
|
| 213 |
-
version = "0.6.
|
| 214 |
source = { registry = "https://pypi.org/simple" }
|
| 215 |
dependencies = [
|
| 216 |
{ name = "gitpython" },
|
|
@@ -220,73 +220,73 @@ dependencies = [
|
|
| 220 |
{ name = "tiktoken" },
|
| 221 |
{ name = "typer" },
|
| 222 |
]
|
| 223 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 224 |
wheels = [
|
| 225 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 226 |
]
|
| 227 |
|
| 228 |
[[package]]
|
| 229 |
name = "coverage"
|
| 230 |
-
version = "7.
|
| 231 |
-
source = { registry = "https://pypi.org/simple" }
|
| 232 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 233 |
-
wheels = [
|
| 234 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 235 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 236 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 237 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 238 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 239 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 240 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 241 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 242 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 243 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 244 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 245 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 246 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 247 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 248 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 249 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 250 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 251 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 252 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 253 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 254 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 255 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 256 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 257 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 258 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 259 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 260 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 261 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 262 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 263 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 264 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 265 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 266 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 267 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 268 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 269 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 270 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 271 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 272 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 273 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 274 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 275 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 276 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 277 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 278 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 279 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 280 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 281 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 282 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 283 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 284 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 285 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 286 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 287 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 288 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 289 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 290 |
]
|
| 291 |
|
| 292 |
[package.optional-dependencies]
|
|
@@ -296,49 +296,49 @@ toml = [
|
|
| 296 |
|
| 297 |
[[package]]
|
| 298 |
name = "cryptography"
|
| 299 |
-
version = "45.0.
|
| 300 |
source = { registry = "https://pypi.org/simple" }
|
| 301 |
dependencies = [
|
| 302 |
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
| 303 |
]
|
| 304 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 305 |
-
wheels = [
|
| 306 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 307 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 308 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 309 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 310 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 311 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 312 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 313 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 314 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 315 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 316 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 317 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 318 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 319 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 320 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 321 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 322 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 323 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 324 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 325 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 326 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 327 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 328 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 329 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 330 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 331 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 332 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 333 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 334 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 335 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 336 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 337 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 338 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 339 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 340 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 341 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 342 |
]
|
| 343 |
|
| 344 |
[[package]]
|
|
@@ -709,7 +709,7 @@ wheels = [
|
|
| 709 |
|
| 710 |
[[package]]
|
| 711 |
name = "mcp"
|
| 712 |
-
version = "1.9.
|
| 713 |
source = { registry = "https://pypi.org/simple" }
|
| 714 |
dependencies = [
|
| 715 |
{ name = "anyio" },
|
|
@@ -722,9 +722,9 @@ dependencies = [
|
|
| 722 |
{ name = "starlette" },
|
| 723 |
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
|
| 724 |
]
|
| 725 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 726 |
wheels = [
|
| 727 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 728 |
]
|
| 729 |
|
| 730 |
[[package]]
|
|
@@ -884,7 +884,7 @@ wheels = [
|
|
| 884 |
|
| 885 |
[[package]]
|
| 886 |
name = "pydantic"
|
| 887 |
-
version = "2.11.
|
| 888 |
source = { registry = "https://pypi.org/simple" }
|
| 889 |
dependencies = [
|
| 890 |
{ name = "annotated-types" },
|
|
@@ -892,9 +892,9 @@ dependencies = [
|
|
| 892 |
{ name = "typing-extensions" },
|
| 893 |
{ name = "typing-inspection" },
|
| 894 |
]
|
| 895 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 896 |
wheels = [
|
| 897 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 898 |
]
|
| 899 |
|
| 900 |
[[package]]
|
|
@@ -1089,20 +1089,20 @@ wheels = [
|
|
| 1089 |
|
| 1090 |
[[package]]
|
| 1091 |
name = "pyright"
|
| 1092 |
-
version = "1.1.
|
| 1093 |
source = { registry = "https://pypi.org/simple" }
|
| 1094 |
dependencies = [
|
| 1095 |
{ name = "nodeenv" },
|
| 1096 |
{ name = "typing-extensions" },
|
| 1097 |
]
|
| 1098 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 1099 |
wheels = [
|
| 1100 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1101 |
]
|
| 1102 |
|
| 1103 |
[[package]]
|
| 1104 |
name = "pytest"
|
| 1105 |
-
version = "8.
|
| 1106 |
source = { registry = "https://pypi.org/simple" }
|
| 1107 |
dependencies = [
|
| 1108 |
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
@@ -1110,11 +1110,12 @@ dependencies = [
|
|
| 1110 |
{ name = "iniconfig" },
|
| 1111 |
{ name = "packaging" },
|
| 1112 |
{ name = "pluggy" },
|
|
|
|
| 1113 |
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
| 1114 |
]
|
| 1115 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 1116 |
wheels = [
|
| 1117 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1118 |
]
|
| 1119 |
|
| 1120 |
[[package]]
|
|
@@ -1131,15 +1132,16 @@ wheels = [
|
|
| 1131 |
|
| 1132 |
[[package]]
|
| 1133 |
name = "pytest-cov"
|
| 1134 |
-
version = "6.
|
| 1135 |
source = { registry = "https://pypi.org/simple" }
|
| 1136 |
dependencies = [
|
| 1137 |
{ name = "coverage", extra = ["toml"] },
|
|
|
|
| 1138 |
{ name = "pytest" },
|
| 1139 |
]
|
| 1140 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 1141 |
wheels = [
|
| 1142 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1143 |
]
|
| 1144 |
|
| 1145 |
[[package]]
|
|
@@ -1347,7 +1349,7 @@ wheels = [
|
|
| 1347 |
|
| 1348 |
[[package]]
|
| 1349 |
name = "requests"
|
| 1350 |
-
version = "2.32.
|
| 1351 |
source = { registry = "https://pypi.org/simple" }
|
| 1352 |
dependencies = [
|
| 1353 |
{ name = "certifi" },
|
|
@@ -1355,9 +1357,9 @@ dependencies = [
|
|
| 1355 |
{ name = "idna" },
|
| 1356 |
{ name = "urllib3" },
|
| 1357 |
]
|
| 1358 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 1359 |
wheels = [
|
| 1360 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1361 |
]
|
| 1362 |
|
| 1363 |
[[package]]
|
|
@@ -1376,27 +1378,27 @@ wheels = [
|
|
| 1376 |
|
| 1377 |
[[package]]
|
| 1378 |
name = "ruff"
|
| 1379 |
-
version = "0.11.
|
| 1380 |
-
source = { registry = "https://pypi.org/simple" }
|
| 1381 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 1382 |
-
wheels = [
|
| 1383 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1384 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1385 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1386 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1387 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1388 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1389 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1390 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1391 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1392 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1393 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1394 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1395 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1396 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1397 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1398 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1399 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1400 |
]
|
| 1401 |
|
| 1402 |
[[package]]
|
|
@@ -1565,11 +1567,11 @@ wheels = [
|
|
| 1565 |
|
| 1566 |
[[package]]
|
| 1567 |
name = "typing-extensions"
|
| 1568 |
-
version = "4.
|
| 1569 |
source = { registry = "https://pypi.org/simple" }
|
| 1570 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 1571 |
wheels = [
|
| 1572 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1573 |
]
|
| 1574 |
|
| 1575 |
[[package]]
|
|
@@ -1595,16 +1597,16 @@ wheels = [
|
|
| 1595 |
|
| 1596 |
[[package]]
|
| 1597 |
name = "uvicorn"
|
| 1598 |
-
version = "0.34.
|
| 1599 |
source = { registry = "https://pypi.org/simple" }
|
| 1600 |
dependencies = [
|
| 1601 |
{ name = "click" },
|
| 1602 |
{ name = "h11" },
|
| 1603 |
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
| 1604 |
]
|
| 1605 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 1606 |
wheels = [
|
| 1607 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1608 |
]
|
| 1609 |
|
| 1610 |
[[package]]
|
|
|
|
| 210 |
|
| 211 |
[[package]]
|
| 212 |
name = "copychat"
|
| 213 |
+
version = "0.6.3"
|
| 214 |
source = { registry = "https://pypi.org/simple" }
|
| 215 |
dependencies = [
|
| 216 |
{ name = "gitpython" },
|
|
|
|
| 220 |
{ name = "tiktoken" },
|
| 221 |
{ name = "typer" },
|
| 222 |
]
|
| 223 |
+
sdist = { url = "https://files.pythonhosted.org/packages/06/d9/112fd77fdc21e89dee79583d326edca3597493be5666281b87e393de2cf9/copychat-0.6.3.tar.gz", hash = "sha256:39ffb493506f20e72d26673490d5a7228cf40f3712d6a60ad6a9ac9f7106f5e4", size = 78328, upload-time = "2025-06-03T15:53:13.368Z" }
|
| 224 |
wheels = [
|
| 225 |
+
{ url = "https://files.pythonhosted.org/packages/05/0b/e2f61f7bba857850b5022ce609ba9fcff8308458f1608ec20b35132263b9/copychat-0.6.3-py3-none-any.whl", hash = "sha256:1460cd02c09b6495550f6a4aa2ab0bacbf2b95176e876fc268b35d22243a4d97", size = 21617, upload-time = "2025-06-03T15:53:11.378Z" },
|
| 226 |
]
|
| 227 |
|
| 228 |
[[package]]
|
| 229 |
name = "coverage"
|
| 230 |
+
version = "7.9.1"
|
| 231 |
+
source = { registry = "https://pypi.org/simple" }
|
| 232 |
+
sdist = { url = "https://files.pythonhosted.org/packages/e7/e0/98670a80884f64578f0c22cd70c5e81a6e07b08167721c7487b4d70a7ca0/coverage-7.9.1.tar.gz", hash = "sha256:6cf43c78c4282708a28e466316935ec7489a9c487518a77fa68f716c67909cec", size = 813650, upload-time = "2025-06-13T13:02:28.627Z" }
|
| 233 |
+
wheels = [
|
| 234 |
+
{ url = "https://files.pythonhosted.org/packages/c1/78/1c1c5ec58f16817c09cbacb39783c3655d54a221b6552f47ff5ac9297603/coverage-7.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc94d7c5e8423920787c33d811c0be67b7be83c705f001f7180c7b186dcf10ca", size = 212028, upload-time = "2025-06-13T13:00:29.293Z" },
|
| 235 |
+
{ url = "https://files.pythonhosted.org/packages/98/db/e91b9076f3a888e3b4ad7972ea3842297a52cc52e73fd1e529856e473510/coverage-7.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16aa0830d0c08a2c40c264cef801db8bc4fc0e1892782e45bcacbd5889270509", size = 212420, upload-time = "2025-06-13T13:00:34.027Z" },
|
| 236 |
+
{ url = "https://files.pythonhosted.org/packages/0e/d0/2b3733412954576b0aea0a16c3b6b8fbe95eb975d8bfa10b07359ead4252/coverage-7.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf95981b126f23db63e9dbe4cf65bd71f9a6305696fa5e2262693bc4e2183f5b", size = 241529, upload-time = "2025-06-13T13:00:35.786Z" },
|
| 237 |
+
{ url = "https://files.pythonhosted.org/packages/b3/00/5e2e5ae2e750a872226a68e984d4d3f3563cb01d1afb449a17aa819bc2c4/coverage-7.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f05031cf21699785cd47cb7485f67df619e7bcdae38e0fde40d23d3d0210d3c3", size = 239403, upload-time = "2025-06-13T13:00:37.399Z" },
|
| 238 |
+
{ url = "https://files.pythonhosted.org/packages/37/3b/a2c27736035156b0a7c20683afe7df498480c0dfdf503b8c878a21b6d7fb/coverage-7.9.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb4fbcab8764dc072cb651a4bcda4d11fb5658a1d8d68842a862a6610bd8cfa3", size = 240548, upload-time = "2025-06-13T13:00:39.647Z" },
|
| 239 |
+
{ url = "https://files.pythonhosted.org/packages/98/f5/13d5fc074c3c0e0dc80422d9535814abf190f1254d7c3451590dc4f8b18c/coverage-7.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0f16649a7330ec307942ed27d06ee7e7a38417144620bb3d6e9a18ded8a2d3e5", size = 240459, upload-time = "2025-06-13T13:00:40.934Z" },
|
| 240 |
+
{ url = "https://files.pythonhosted.org/packages/36/24/24b9676ea06102df824c4a56ffd13dc9da7904478db519efa877d16527d5/coverage-7.9.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cea0a27a89e6432705fffc178064503508e3c0184b4f061700e771a09de58187", size = 239128, upload-time = "2025-06-13T13:00:42.343Z" },
|
| 241 |
+
{ url = "https://files.pythonhosted.org/packages/be/05/242b7a7d491b369ac5fee7908a6e5ba42b3030450f3ad62c645b40c23e0e/coverage-7.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e980b53a959fa53b6f05343afbd1e6f44a23ed6c23c4b4c56c6662bbb40c82ce", size = 239402, upload-time = "2025-06-13T13:00:43.634Z" },
|
| 242 |
+
{ url = "https://files.pythonhosted.org/packages/73/e0/4de7f87192fa65c9c8fbaeb75507e124f82396b71de1797da5602898be32/coverage-7.9.1-cp310-cp310-win32.whl", hash = "sha256:70760b4c5560be6ca70d11f8988ee6542b003f982b32f83d5ac0b72476607b70", size = 214518, upload-time = "2025-06-13T13:00:45.622Z" },
|
| 243 |
+
{ url = "https://files.pythonhosted.org/packages/d5/ab/5e4e2fe458907d2a65fab62c773671cfc5ac704f1e7a9ddd91996f66e3c2/coverage-7.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a66e8f628b71f78c0e0342003d53b53101ba4e00ea8dabb799d9dba0abbbcebe", size = 215436, upload-time = "2025-06-13T13:00:47.245Z" },
|
| 244 |
+
{ url = "https://files.pythonhosted.org/packages/60/34/fa69372a07d0903a78ac103422ad34db72281c9fc625eba94ac1185da66f/coverage-7.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:95c765060e65c692da2d2f51a9499c5e9f5cf5453aeaf1420e3fc847cc060582", size = 212146, upload-time = "2025-06-13T13:00:48.496Z" },
|
| 245 |
+
{ url = "https://files.pythonhosted.org/packages/27/f0/da1894915d2767f093f081c42afeba18e760f12fdd7a2f4acbe00564d767/coverage-7.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba383dc6afd5ec5b7a0d0c23d38895db0e15bcba7fb0fa8901f245267ac30d86", size = 212536, upload-time = "2025-06-13T13:00:51.535Z" },
|
| 246 |
+
{ url = "https://files.pythonhosted.org/packages/10/d5/3fc33b06e41e390f88eef111226a24e4504d216ab8e5d1a7089aa5a3c87a/coverage-7.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37ae0383f13cbdcf1e5e7014489b0d71cc0106458878ccde52e8a12ced4298ed", size = 245092, upload-time = "2025-06-13T13:00:52.883Z" },
|
| 247 |
+
{ url = "https://files.pythonhosted.org/packages/0a/39/7aa901c14977aba637b78e95800edf77f29f5a380d29768c5b66f258305b/coverage-7.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69aa417a030bf11ec46149636314c24c8d60fadb12fc0ee8f10fda0d918c879d", size = 242806, upload-time = "2025-06-13T13:00:54.571Z" },
|
| 248 |
+
{ url = "https://files.pythonhosted.org/packages/43/fc/30e5cfeaf560b1fc1989227adedc11019ce4bb7cce59d65db34fe0c2d963/coverage-7.9.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a4be2a28656afe279b34d4f91c3e26eccf2f85500d4a4ff0b1f8b54bf807338", size = 244610, upload-time = "2025-06-13T13:00:56.932Z" },
|
| 249 |
+
{ url = "https://files.pythonhosted.org/packages/bf/15/cca62b13f39650bc87b2b92bb03bce7f0e79dd0bf2c7529e9fc7393e4d60/coverage-7.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:382e7ddd5289f140259b610e5f5c58f713d025cb2f66d0eb17e68d0a94278875", size = 244257, upload-time = "2025-06-13T13:00:58.545Z" },
|
| 250 |
+
{ url = "https://files.pythonhosted.org/packages/cd/1a/c0f2abe92c29e1464dbd0ff9d56cb6c88ae2b9e21becdb38bea31fcb2f6c/coverage-7.9.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e5532482344186c543c37bfad0ee6069e8ae4fc38d073b8bc836fc8f03c9e250", size = 242309, upload-time = "2025-06-13T13:00:59.836Z" },
|
| 251 |
+
{ url = "https://files.pythonhosted.org/packages/57/8d/c6fd70848bd9bf88fa90df2af5636589a8126d2170f3aade21ed53f2b67a/coverage-7.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a39d18b3f50cc121d0ce3838d32d58bd1d15dab89c910358ebefc3665712256c", size = 242898, upload-time = "2025-06-13T13:01:02.506Z" },
|
| 252 |
+
{ url = "https://files.pythonhosted.org/packages/c2/9e/6ca46c7bff4675f09a66fe2797cd1ad6a24f14c9c7c3b3ebe0470a6e30b8/coverage-7.9.1-cp311-cp311-win32.whl", hash = "sha256:dd24bd8d77c98557880def750782df77ab2b6885a18483dc8588792247174b32", size = 214561, upload-time = "2025-06-13T13:01:04.012Z" },
|
| 253 |
+
{ url = "https://files.pythonhosted.org/packages/a1/30/166978c6302010742dabcdc425fa0f938fa5a800908e39aff37a7a876a13/coverage-7.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:6b55ad10a35a21b8015eabddc9ba31eb590f54adc9cd39bcf09ff5349fd52125", size = 215493, upload-time = "2025-06-13T13:01:05.702Z" },
|
| 254 |
+
{ url = "https://files.pythonhosted.org/packages/60/07/a6d2342cd80a5be9f0eeab115bc5ebb3917b4a64c2953534273cf9bc7ae6/coverage-7.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ad935f0016be24c0e97fc8c40c465f9c4b85cbbe6eac48934c0dc4d2568321e", size = 213869, upload-time = "2025-06-13T13:01:09.345Z" },
|
| 255 |
+
{ url = "https://files.pythonhosted.org/packages/68/d9/7f66eb0a8f2fce222de7bdc2046ec41cb31fe33fb55a330037833fb88afc/coverage-7.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8de12b4b87c20de895f10567639c0797b621b22897b0af3ce4b4e204a743626", size = 212336, upload-time = "2025-06-13T13:01:10.909Z" },
|
| 256 |
+
{ url = "https://files.pythonhosted.org/packages/20/20/e07cb920ef3addf20f052ee3d54906e57407b6aeee3227a9c91eea38a665/coverage-7.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5add197315a054e92cee1b5f686a2bcba60c4c3e66ee3de77ace6c867bdee7cb", size = 212571, upload-time = "2025-06-13T13:01:12.518Z" },
|
| 257 |
+
{ url = "https://files.pythonhosted.org/packages/78/f8/96f155de7e9e248ca9c8ff1a40a521d944ba48bec65352da9be2463745bf/coverage-7.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:600a1d4106fe66f41e5d0136dfbc68fe7200a5cbe85610ddf094f8f22e1b0300", size = 246377, upload-time = "2025-06-13T13:01:14.87Z" },
|
| 258 |
+
{ url = "https://files.pythonhosted.org/packages/3e/cf/1d783bd05b7bca5c10ded5f946068909372e94615a4416afadfe3f63492d/coverage-7.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a876e4c3e5a2a1715a6608906aa5a2e0475b9c0f68343c2ada98110512ab1d8", size = 243394, upload-time = "2025-06-13T13:01:16.23Z" },
|
| 259 |
+
{ url = "https://files.pythonhosted.org/packages/02/dd/e7b20afd35b0a1abea09fb3998e1abc9f9bd953bee548f235aebd2b11401/coverage-7.9.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81f34346dd63010453922c8e628a52ea2d2ccd73cb2487f7700ac531b247c8a5", size = 245586, upload-time = "2025-06-13T13:01:17.532Z" },
|
| 260 |
+
{ url = "https://files.pythonhosted.org/packages/4e/38/b30b0006fea9d617d1cb8e43b1bc9a96af11eff42b87eb8c716cf4d37469/coverage-7.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:888f8eee13f2377ce86d44f338968eedec3291876b0b8a7289247ba52cb984cd", size = 245396, upload-time = "2025-06-13T13:01:19.164Z" },
|
| 261 |
+
{ url = "https://files.pythonhosted.org/packages/31/e4/4d8ec1dc826e16791f3daf1b50943e8e7e1eb70e8efa7abb03936ff48418/coverage-7.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9969ef1e69b8c8e1e70d591f91bbc37fc9a3621e447525d1602801a24ceda898", size = 243577, upload-time = "2025-06-13T13:01:22.433Z" },
|
| 262 |
+
{ url = "https://files.pythonhosted.org/packages/25/f4/b0e96c5c38e6e40ef465c4bc7f138863e2909c00e54a331da335faf0d81a/coverage-7.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:60c458224331ee3f1a5b472773e4a085cc27a86a0b48205409d364272d67140d", size = 244809, upload-time = "2025-06-13T13:01:24.143Z" },
|
| 263 |
+
{ url = "https://files.pythonhosted.org/packages/8a/65/27e0a1fa5e2e5079bdca4521be2f5dabf516f94e29a0defed35ac2382eb2/coverage-7.9.1-cp312-cp312-win32.whl", hash = "sha256:5f646a99a8c2b3ff4c6a6e081f78fad0dde275cd59f8f49dc4eab2e394332e74", size = 214724, upload-time = "2025-06-13T13:01:25.435Z" },
|
| 264 |
+
{ url = "https://files.pythonhosted.org/packages/9b/a8/d5b128633fd1a5e0401a4160d02fa15986209a9e47717174f99dc2f7166d/coverage-7.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:30f445f85c353090b83e552dcbbdad3ec84c7967e108c3ae54556ca69955563e", size = 215535, upload-time = "2025-06-13T13:01:27.861Z" },
|
| 265 |
+
{ url = "https://files.pythonhosted.org/packages/a3/37/84bba9d2afabc3611f3e4325ee2c6a47cd449b580d4a606b240ce5a6f9bf/coverage-7.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:af41da5dca398d3474129c58cb2b106a5d93bbb196be0d307ac82311ca234342", size = 213904, upload-time = "2025-06-13T13:01:29.202Z" },
|
| 266 |
+
{ url = "https://files.pythonhosted.org/packages/d0/a7/a027970c991ca90f24e968999f7d509332daf6b8c3533d68633930aaebac/coverage-7.9.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:31324f18d5969feef7344a932c32428a2d1a3e50b15a6404e97cba1cc9b2c631", size = 212358, upload-time = "2025-06-13T13:01:30.909Z" },
|
| 267 |
+
{ url = "https://files.pythonhosted.org/packages/f2/48/6aaed3651ae83b231556750280682528fea8ac7f1232834573472d83e459/coverage-7.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0c804506d624e8a20fb3108764c52e0eef664e29d21692afa375e0dd98dc384f", size = 212620, upload-time = "2025-06-13T13:01:32.256Z" },
|
| 268 |
+
{ url = "https://files.pythonhosted.org/packages/6c/2a/f4b613f3b44d8b9f144847c89151992b2b6b79cbc506dee89ad0c35f209d/coverage-7.9.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef64c27bc40189f36fcc50c3fb8f16ccda73b6a0b80d9bd6e6ce4cffcd810bbd", size = 245788, upload-time = "2025-06-13T13:01:33.948Z" },
|
| 269 |
+
{ url = "https://files.pythonhosted.org/packages/04/d2/de4fdc03af5e4e035ef420ed26a703c6ad3d7a07aff2e959eb84e3b19ca8/coverage-7.9.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4fe2348cc6ec372e25adec0219ee2334a68d2f5222e0cba9c0d613394e12d86", size = 243001, upload-time = "2025-06-13T13:01:35.285Z" },
|
| 270 |
+
{ url = "https://files.pythonhosted.org/packages/f5/e8/eed18aa5583b0423ab7f04e34659e51101135c41cd1dcb33ac1d7013a6d6/coverage-7.9.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34ed2186fe52fcc24d4561041979a0dec69adae7bce2ae8d1c49eace13e55c43", size = 244985, upload-time = "2025-06-13T13:01:36.712Z" },
|
| 271 |
+
{ url = "https://files.pythonhosted.org/packages/17/f8/ae9e5cce8885728c934eaa58ebfa8281d488ef2afa81c3dbc8ee9e6d80db/coverage-7.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:25308bd3d00d5eedd5ae7d4357161f4df743e3c0240fa773ee1b0f75e6c7c0f1", size = 245152, upload-time = "2025-06-13T13:01:39.303Z" },
|
| 272 |
+
{ url = "https://files.pythonhosted.org/packages/5a/c8/272c01ae792bb3af9b30fac14d71d63371db227980682836ec388e2c57c0/coverage-7.9.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:73e9439310f65d55a5a1e0564b48e34f5369bee943d72c88378f2d576f5a5751", size = 243123, upload-time = "2025-06-13T13:01:40.727Z" },
|
| 273 |
+
{ url = "https://files.pythonhosted.org/packages/8c/d0/2819a1e3086143c094ab446e3bdf07138527a7b88cb235c488e78150ba7a/coverage-7.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37ab6be0859141b53aa89412a82454b482c81cf750de4f29223d52268a86de67", size = 244506, upload-time = "2025-06-13T13:01:42.184Z" },
|
| 274 |
+
{ url = "https://files.pythonhosted.org/packages/8b/4e/9f6117b89152df7b6112f65c7a4ed1f2f5ec8e60c4be8f351d91e7acc848/coverage-7.9.1-cp313-cp313-win32.whl", hash = "sha256:64bdd969456e2d02a8b08aa047a92d269c7ac1f47e0c977675d550c9a0863643", size = 214766, upload-time = "2025-06-13T13:01:44.482Z" },
|
| 275 |
+
{ url = "https://files.pythonhosted.org/packages/27/0f/4b59f7c93b52c2c4ce7387c5a4e135e49891bb3b7408dcc98fe44033bbe0/coverage-7.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:be9e3f68ca9edb897c2184ad0eee815c635565dbe7a0e7e814dc1f7cbab92c0a", size = 215568, upload-time = "2025-06-13T13:01:45.772Z" },
|
| 276 |
+
{ url = "https://files.pythonhosted.org/packages/09/1e/9679826336f8c67b9c39a359352882b24a8a7aee48d4c9cad08d38d7510f/coverage-7.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:1c503289ffef1d5105d91bbb4d62cbe4b14bec4d13ca225f9c73cde9bb46207d", size = 213939, upload-time = "2025-06-13T13:01:47.087Z" },
|
| 277 |
+
{ url = "https://files.pythonhosted.org/packages/bb/5b/5c6b4e7a407359a2e3b27bf9c8a7b658127975def62077d441b93a30dbe8/coverage-7.9.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0b3496922cb5f4215bf5caaef4cf12364a26b0be82e9ed6d050f3352cf2d7ef0", size = 213079, upload-time = "2025-06-13T13:01:48.554Z" },
|
| 278 |
+
{ url = "https://files.pythonhosted.org/packages/a2/22/1e2e07279fd2fd97ae26c01cc2186e2258850e9ec125ae87184225662e89/coverage-7.9.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9565c3ab1c93310569ec0d86b017f128f027cab0b622b7af288696d7ed43a16d", size = 213299, upload-time = "2025-06-13T13:01:49.997Z" },
|
| 279 |
+
{ url = "https://files.pythonhosted.org/packages/14/c0/4c5125a4b69d66b8c85986d3321520f628756cf524af810baab0790c7647/coverage-7.9.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2241ad5dbf79ae1d9c08fe52b36d03ca122fb9ac6bca0f34439e99f8327ac89f", size = 256535, upload-time = "2025-06-13T13:01:51.314Z" },
|
| 280 |
+
{ url = "https://files.pythonhosted.org/packages/81/8b/e36a04889dda9960be4263e95e777e7b46f1bb4fc32202612c130a20c4da/coverage-7.9.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bb5838701ca68b10ebc0937dbd0eb81974bac54447c55cd58dea5bca8451029", size = 252756, upload-time = "2025-06-13T13:01:54.403Z" },
|
| 281 |
+
{ url = "https://files.pythonhosted.org/packages/98/82/be04eff8083a09a4622ecd0e1f31a2c563dbea3ed848069e7b0445043a70/coverage-7.9.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b30a25f814591a8c0c5372c11ac8967f669b97444c47fd794926e175c4047ece", size = 254912, upload-time = "2025-06-13T13:01:56.769Z" },
|
| 282 |
+
{ url = "https://files.pythonhosted.org/packages/0f/25/c26610a2c7f018508a5ab958e5b3202d900422cf7cdca7670b6b8ca4e8df/coverage-7.9.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2d04b16a6062516df97969f1ae7efd0de9c31eb6ebdceaa0d213b21c0ca1a683", size = 256144, upload-time = "2025-06-13T13:01:58.19Z" },
|
| 283 |
+
{ url = "https://files.pythonhosted.org/packages/c5/8b/fb9425c4684066c79e863f1e6e7ecebb49e3a64d9f7f7860ef1688c56f4a/coverage-7.9.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7931b9e249edefb07cd6ae10c702788546341d5fe44db5b6108a25da4dca513f", size = 254257, upload-time = "2025-06-13T13:01:59.645Z" },
|
| 284 |
+
{ url = "https://files.pythonhosted.org/packages/93/df/27b882f54157fc1131e0e215b0da3b8d608d9b8ef79a045280118a8f98fe/coverage-7.9.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52e92b01041151bf607ee858e5a56c62d4b70f4dac85b8c8cb7fb8a351ab2c10", size = 255094, upload-time = "2025-06-13T13:02:01.37Z" },
|
| 285 |
+
{ url = "https://files.pythonhosted.org/packages/41/5f/cad1c3dbed8b3ee9e16fa832afe365b4e3eeab1fb6edb65ebbf745eabc92/coverage-7.9.1-cp313-cp313t-win32.whl", hash = "sha256:684e2110ed84fd1ca5f40e89aa44adf1729dc85444004111aa01866507adf363", size = 215437, upload-time = "2025-06-13T13:02:02.905Z" },
|
| 286 |
+
{ url = "https://files.pythonhosted.org/packages/99/4d/fad293bf081c0e43331ca745ff63673badc20afea2104b431cdd8c278b4c/coverage-7.9.1-cp313-cp313t-win_amd64.whl", hash = "sha256:437c576979e4db840539674e68c84b3cda82bc824dd138d56bead1435f1cb5d7", size = 216605, upload-time = "2025-06-13T13:02:05.638Z" },
|
| 287 |
+
{ url = "https://files.pythonhosted.org/packages/1f/56/4ee027d5965fc7fc126d7ec1187529cc30cc7d740846e1ecb5e92d31b224/coverage-7.9.1-cp313-cp313t-win_arm64.whl", hash = "sha256:18a0912944d70aaf5f399e350445738a1a20b50fbea788f640751c2ed9208b6c", size = 214392, upload-time = "2025-06-13T13:02:07.642Z" },
|
| 288 |
+
{ url = "https://files.pythonhosted.org/packages/3e/e5/c723545c3fd3204ebde3b4cc4b927dce709d3b6dc577754bb57f63ca4a4a/coverage-7.9.1-pp39.pp310.pp311-none-any.whl", hash = "sha256:db0f04118d1db74db6c9e1cb1898532c7dcc220f1d2718f058601f7c3f499514", size = 204009, upload-time = "2025-06-13T13:02:25.787Z" },
|
| 289 |
+
{ url = "https://files.pythonhosted.org/packages/08/b8/7ddd1e8ba9701dea08ce22029917140e6f66a859427406579fd8d0ca7274/coverage-7.9.1-py3-none-any.whl", hash = "sha256:66b974b145aa189516b6bf2d8423e888b742517d37872f6ee4c5be0073bd9a3c", size = 204000, upload-time = "2025-06-13T13:02:27.173Z" },
|
| 290 |
]
|
| 291 |
|
| 292 |
[package.optional-dependencies]
|
|
|
|
| 296 |
|
| 297 |
[[package]]
|
| 298 |
name = "cryptography"
|
| 299 |
+
version = "45.0.4"
|
| 300 |
source = { registry = "https://pypi.org/simple" }
|
| 301 |
dependencies = [
|
| 302 |
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
| 303 |
]
|
| 304 |
+
sdist = { url = "https://files.pythonhosted.org/packages/fe/c8/a2a376a8711c1e11708b9c9972e0c3223f5fc682552c82d8db844393d6ce/cryptography-45.0.4.tar.gz", hash = "sha256:7405ade85c83c37682c8fe65554759800a4a8c54b2d96e0f8ad114d31b808d57", size = 744890, upload-time = "2025-06-10T00:03:51.297Z" }
|
| 305 |
+
wheels = [
|
| 306 |
+
{ url = "https://files.pythonhosted.org/packages/cc/1c/92637793de053832523b410dbe016d3f5c11b41d0cf6eef8787aabb51d41/cryptography-45.0.4-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:425a9a6ac2823ee6e46a76a21a4e8342d8fa5c01e08b823c1f19a8b74f096069", size = 7055712, upload-time = "2025-06-10T00:02:38.826Z" },
|
| 307 |
+
{ url = "https://files.pythonhosted.org/packages/ba/14/93b69f2af9ba832ad6618a03f8a034a5851dc9a3314336a3d71c252467e1/cryptography-45.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:680806cf63baa0039b920f4976f5f31b10e772de42f16310a6839d9f21a26b0d", size = 4205335, upload-time = "2025-06-10T00:02:41.64Z" },
|
| 308 |
+
{ url = "https://files.pythonhosted.org/packages/67/30/fae1000228634bf0b647fca80403db5ca9e3933b91dd060570689f0bd0f7/cryptography-45.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4ca0f52170e821bc8da6fc0cc565b7bb8ff8d90d36b5e9fdd68e8a86bdf72036", size = 4431487, upload-time = "2025-06-10T00:02:43.696Z" },
|
| 309 |
+
{ url = "https://files.pythonhosted.org/packages/6d/5a/7dffcf8cdf0cb3c2430de7404b327e3db64735747d641fc492539978caeb/cryptography-45.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f3fe7a5ae34d5a414957cc7f457e2b92076e72938423ac64d215722f6cf49a9e", size = 4208922, upload-time = "2025-06-10T00:02:45.334Z" },
|
| 310 |
+
{ url = "https://files.pythonhosted.org/packages/c6/f3/528729726eb6c3060fa3637253430547fbaaea95ab0535ea41baa4a6fbd8/cryptography-45.0.4-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:25eb4d4d3e54595dc8adebc6bbd5623588991d86591a78c2548ffb64797341e2", size = 3900433, upload-time = "2025-06-10T00:02:47.359Z" },
|
| 311 |
+
{ url = "https://files.pythonhosted.org/packages/d9/4a/67ba2e40f619e04d83c32f7e1d484c1538c0800a17c56a22ff07d092ccc1/cryptography-45.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ce1678a2ccbe696cf3af15a75bb72ee008d7ff183c9228592ede9db467e64f1b", size = 4464163, upload-time = "2025-06-10T00:02:49.412Z" },
|
| 312 |
+
{ url = "https://files.pythonhosted.org/packages/7e/9a/b4d5aa83661483ac372464809c4b49b5022dbfe36b12fe9e323ca8512420/cryptography-45.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:49fe9155ab32721b9122975e168a6760d8ce4cffe423bcd7ca269ba41b5dfac1", size = 4208687, upload-time = "2025-06-10T00:02:50.976Z" },
|
| 313 |
+
{ url = "https://files.pythonhosted.org/packages/db/b7/a84bdcd19d9c02ec5807f2ec2d1456fd8451592c5ee353816c09250e3561/cryptography-45.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2882338b2a6e0bd337052e8b9007ced85c637da19ef9ecaf437744495c8c2999", size = 4463623, upload-time = "2025-06-10T00:02:52.542Z" },
|
| 314 |
+
{ url = "https://files.pythonhosted.org/packages/d8/84/69707d502d4d905021cac3fb59a316344e9f078b1da7fb43ecde5e10840a/cryptography-45.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:23b9c3ea30c3ed4db59e7b9619272e94891f8a3a5591d0b656a7582631ccf750", size = 4332447, upload-time = "2025-06-10T00:02:54.63Z" },
|
| 315 |
+
{ url = "https://files.pythonhosted.org/packages/f3/ee/d4f2ab688e057e90ded24384e34838086a9b09963389a5ba6854b5876598/cryptography-45.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0a97c927497e3bc36b33987abb99bf17a9a175a19af38a892dc4bbb844d7ee2", size = 4572830, upload-time = "2025-06-10T00:02:56.689Z" },
|
| 316 |
+
{ url = "https://files.pythonhosted.org/packages/70/d4/994773a261d7ff98034f72c0e8251fe2755eac45e2265db4c866c1c6829c/cryptography-45.0.4-cp311-abi3-win32.whl", hash = "sha256:e00a6c10a5c53979d6242f123c0a97cff9f3abed7f064fc412c36dc521b5f257", size = 2932769, upload-time = "2025-06-10T00:02:58.467Z" },
|
| 317 |
+
{ url = "https://files.pythonhosted.org/packages/5a/42/c80bd0b67e9b769b364963b5252b17778a397cefdd36fa9aa4a5f34c599a/cryptography-45.0.4-cp311-abi3-win_amd64.whl", hash = "sha256:817ee05c6c9f7a69a16200f0c90ab26d23a87701e2a284bd15156783e46dbcc8", size = 3410441, upload-time = "2025-06-10T00:03:00.14Z" },
|
| 318 |
+
{ url = "https://files.pythonhosted.org/packages/ce/0b/2488c89f3a30bc821c9d96eeacfcab6ff3accc08a9601ba03339c0fd05e5/cryptography-45.0.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:964bcc28d867e0f5491a564b7debb3ffdd8717928d315d12e0d7defa9e43b723", size = 7031836, upload-time = "2025-06-10T00:03:01.726Z" },
|
| 319 |
+
{ url = "https://files.pythonhosted.org/packages/fe/51/8c584ed426093aac257462ae62d26ad61ef1cbf5b58d8b67e6e13c39960e/cryptography-45.0.4-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6a5bf57554e80f75a7db3d4b1dacaa2764611ae166ab42ea9a72bcdb5d577637", size = 4195746, upload-time = "2025-06-10T00:03:03.94Z" },
|
| 320 |
+
{ url = "https://files.pythonhosted.org/packages/5c/7d/4b0ca4d7af95a704eef2f8f80a8199ed236aaf185d55385ae1d1610c03c2/cryptography-45.0.4-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:46cf7088bf91bdc9b26f9c55636492c1cce3e7aaf8041bbf0243f5e5325cfb2d", size = 4424456, upload-time = "2025-06-10T00:03:05.589Z" },
|
| 321 |
+
{ url = "https://files.pythonhosted.org/packages/1d/45/5fabacbc6e76ff056f84d9f60eeac18819badf0cefc1b6612ee03d4ab678/cryptography-45.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7bedbe4cc930fa4b100fc845ea1ea5788fcd7ae9562e669989c11618ae8d76ee", size = 4198495, upload-time = "2025-06-10T00:03:09.172Z" },
|
| 322 |
+
{ url = "https://files.pythonhosted.org/packages/55/b7/ffc9945b290eb0a5d4dab9b7636706e3b5b92f14ee5d9d4449409d010d54/cryptography-45.0.4-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:eaa3e28ea2235b33220b949c5a0d6cf79baa80eab2eb5607ca8ab7525331b9ff", size = 3885540, upload-time = "2025-06-10T00:03:10.835Z" },
|
| 323 |
+
{ url = "https://files.pythonhosted.org/packages/7f/e3/57b010282346980475e77d414080acdcb3dab9a0be63071efc2041a2c6bd/cryptography-45.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7ef2dde4fa9408475038fc9aadfc1fb2676b174e68356359632e980c661ec8f6", size = 4452052, upload-time = "2025-06-10T00:03:12.448Z" },
|
| 324 |
+
{ url = "https://files.pythonhosted.org/packages/37/e6/ddc4ac2558bf2ef517a358df26f45bc774a99bf4653e7ee34b5e749c03e3/cryptography-45.0.4-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:6a3511ae33f09094185d111160fd192c67aa0a2a8d19b54d36e4c78f651dc5ad", size = 4198024, upload-time = "2025-06-10T00:03:13.976Z" },
|
| 325 |
+
{ url = "https://files.pythonhosted.org/packages/3a/c0/85fa358ddb063ec588aed4a6ea1df57dc3e3bc1712d87c8fa162d02a65fc/cryptography-45.0.4-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:06509dc70dd71fa56eaa138336244e2fbaf2ac164fc9b5e66828fccfd2b680d6", size = 4451442, upload-time = "2025-06-10T00:03:16.248Z" },
|
| 326 |
+
{ url = "https://files.pythonhosted.org/packages/33/67/362d6ec1492596e73da24e669a7fbbaeb1c428d6bf49a29f7a12acffd5dc/cryptography-45.0.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5f31e6b0a5a253f6aa49be67279be4a7e5a4ef259a9f33c69f7d1b1191939872", size = 4325038, upload-time = "2025-06-10T00:03:18.4Z" },
|
| 327 |
+
{ url = "https://files.pythonhosted.org/packages/53/75/82a14bf047a96a1b13ebb47fb9811c4f73096cfa2e2b17c86879687f9027/cryptography-45.0.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:944e9ccf67a9594137f942d5b52c8d238b1b4e46c7a0c2891b7ae6e01e7c80a4", size = 4560964, upload-time = "2025-06-10T00:03:20.06Z" },
|
| 328 |
+
{ url = "https://files.pythonhosted.org/packages/cd/37/1a3cba4c5a468ebf9b95523a5ef5651244693dc712001e276682c278fc00/cryptography-45.0.4-cp37-abi3-win32.whl", hash = "sha256:c22fe01e53dc65edd1945a2e6f0015e887f84ced233acecb64b4daadb32f5c97", size = 2924557, upload-time = "2025-06-10T00:03:22.563Z" },
|
| 329 |
+
{ url = "https://files.pythonhosted.org/packages/2a/4b/3256759723b7e66380397d958ca07c59cfc3fb5c794fb5516758afd05d41/cryptography-45.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:627ba1bc94f6adf0b0a2e35d87020285ead22d9f648c7e75bb64f367375f3b22", size = 3395508, upload-time = "2025-06-10T00:03:24.586Z" },
|
| 330 |
+
{ url = "https://files.pythonhosted.org/packages/16/33/b38e9d372afde56906a23839302c19abdac1c505bfb4776c1e4b07c3e145/cryptography-45.0.4-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a77c6fb8d76e9c9f99f2f3437c1a4ac287b34eaf40997cfab1e9bd2be175ac39", size = 3580103, upload-time = "2025-06-10T00:03:26.207Z" },
|
| 331 |
+
{ url = "https://files.pythonhosted.org/packages/c4/b9/357f18064ec09d4807800d05a48f92f3b369056a12f995ff79549fbb31f1/cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7aad98a25ed8ac917fdd8a9c1e706e5a0956e06c498be1f713b61734333a4507", size = 4143732, upload-time = "2025-06-10T00:03:27.896Z" },
|
| 332 |
+
{ url = "https://files.pythonhosted.org/packages/c4/9c/7f7263b03d5db329093617648b9bd55c953de0b245e64e866e560f9aac07/cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3530382a43a0e524bc931f187fc69ef4c42828cf7d7f592f7f249f602b5a4ab0", size = 4385424, upload-time = "2025-06-10T00:03:29.992Z" },
|
| 333 |
+
{ url = "https://files.pythonhosted.org/packages/a6/5a/6aa9d8d5073d5acc0e04e95b2860ef2684b2bd2899d8795fc443013e263b/cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:6b613164cb8425e2f8db5849ffb84892e523bf6d26deb8f9bb76ae86181fa12b", size = 4142438, upload-time = "2025-06-10T00:03:31.782Z" },
|
| 334 |
+
{ url = "https://files.pythonhosted.org/packages/42/1c/71c638420f2cdd96d9c2b287fec515faf48679b33a2b583d0f1eda3a3375/cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:96d4819e25bf3b685199b304a0029ce4a3caf98947ce8a066c9137cc78ad2c58", size = 4384622, upload-time = "2025-06-10T00:03:33.491Z" },
|
| 335 |
+
{ url = "https://files.pythonhosted.org/packages/ef/ab/e3a055c34e97deadbf0d846e189237d3385dca99e1a7e27384c3b2292041/cryptography-45.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b97737a3ffbea79eebb062eb0d67d72307195035332501722a9ca86bab9e3ab2", size = 3328911, upload-time = "2025-06-10T00:03:35.035Z" },
|
| 336 |
+
{ url = "https://files.pythonhosted.org/packages/ea/ba/cf442ae99ef363855ed84b39e0fb3c106ac66b7a7703f3c9c9cfe05412cb/cryptography-45.0.4-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4828190fb6c4bcb6ebc6331f01fe66ae838bb3bd58e753b59d4b22eb444b996c", size = 3590512, upload-time = "2025-06-10T00:03:36.982Z" },
|
| 337 |
+
{ url = "https://files.pythonhosted.org/packages/28/9a/a7d5bb87d149eb99a5abdc69a41e4e47b8001d767e5f403f78bfaafc7aa7/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:03dbff8411206713185b8cebe31bc5c0eb544799a50c09035733716b386e61a4", size = 4146899, upload-time = "2025-06-10T00:03:38.659Z" },
|
| 338 |
+
{ url = "https://files.pythonhosted.org/packages/17/11/9361c2c71c42cc5c465cf294c8030e72fb0c87752bacbd7a3675245e3db3/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51dfbd4d26172d31150d84c19bbe06c68ea4b7f11bbc7b3a5e146b367c311349", size = 4388900, upload-time = "2025-06-10T00:03:40.233Z" },
|
| 339 |
+
{ url = "https://files.pythonhosted.org/packages/c0/76/f95b83359012ee0e670da3e41c164a0c256aeedd81886f878911581d852f/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:0339a692de47084969500ee455e42c58e449461e0ec845a34a6a9b9bf7df7fb8", size = 4146422, upload-time = "2025-06-10T00:03:41.827Z" },
|
| 340 |
+
{ url = "https://files.pythonhosted.org/packages/09/ad/5429fcc4def93e577a5407988f89cf15305e64920203d4ac14601a9dc876/cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:0cf13c77d710131d33e63626bd55ae7c0efb701ebdc2b3a7952b9b23a0412862", size = 4388475, upload-time = "2025-06-10T00:03:43.493Z" },
|
| 341 |
+
{ url = "https://files.pythonhosted.org/packages/99/49/0ab9774f64555a1b50102757811508f5ace451cf5dc0a2d074a4b9deca6a/cryptography-45.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bbc505d1dc469ac12a0a064214879eac6294038d6b24ae9f71faae1448a9608d", size = 3337594, upload-time = "2025-06-10T00:03:45.523Z" },
|
| 342 |
]
|
| 343 |
|
| 344 |
[[package]]
|
|
|
|
| 709 |
|
| 710 |
[[package]]
|
| 711 |
name = "mcp"
|
| 712 |
+
version = "1.9.4"
|
| 713 |
source = { registry = "https://pypi.org/simple" }
|
| 714 |
dependencies = [
|
| 715 |
{ name = "anyio" },
|
|
|
|
| 722 |
{ name = "starlette" },
|
| 723 |
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
|
| 724 |
]
|
| 725 |
+
sdist = { url = "https://files.pythonhosted.org/packages/06/f2/dc2450e566eeccf92d89a00c3e813234ad58e2ba1e31d11467a09ac4f3b9/mcp-1.9.4.tar.gz", hash = "sha256:cfb0bcd1a9535b42edaef89947b9e18a8feb49362e1cc059d6e7fc636f2cb09f", size = 333294, upload-time = "2025-06-12T08:20:30.158Z" }
|
| 726 |
wheels = [
|
| 727 |
+
{ url = "https://files.pythonhosted.org/packages/97/fc/80e655c955137393c443842ffcc4feccab5b12fa7cb8de9ced90f90e6998/mcp-1.9.4-py3-none-any.whl", hash = "sha256:7fcf36b62936adb8e63f89346bccca1268eeca9bf6dfb562ee10b1dfbda9dac0", size = 130232, upload-time = "2025-06-12T08:20:28.551Z" },
|
| 728 |
]
|
| 729 |
|
| 730 |
[[package]]
|
|
|
|
| 884 |
|
| 885 |
[[package]]
|
| 886 |
name = "pydantic"
|
| 887 |
+
version = "2.11.7"
|
| 888 |
source = { registry = "https://pypi.org/simple" }
|
| 889 |
dependencies = [
|
| 890 |
{ name = "annotated-types" },
|
|
|
|
| 892 |
{ name = "typing-extensions" },
|
| 893 |
{ name = "typing-inspection" },
|
| 894 |
]
|
| 895 |
+
sdist = { url = "https://files.pythonhosted.org/packages/00/dd/4325abf92c39ba8623b5af936ddb36ffcfe0beae70405d456ab1fb2f5b8c/pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db", size = 788350, upload-time = "2025-06-14T08:33:17.137Z" }
|
| 896 |
wheels = [
|
| 897 |
+
{ url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" },
|
| 898 |
]
|
| 899 |
|
| 900 |
[[package]]
|
|
|
|
| 1089 |
|
| 1090 |
[[package]]
|
| 1091 |
name = "pyright"
|
| 1092 |
+
version = "1.1.402"
|
| 1093 |
source = { registry = "https://pypi.org/simple" }
|
| 1094 |
dependencies = [
|
| 1095 |
{ name = "nodeenv" },
|
| 1096 |
{ name = "typing-extensions" },
|
| 1097 |
]
|
| 1098 |
+
sdist = { url = "https://files.pythonhosted.org/packages/aa/04/ce0c132d00e20f2d2fb3b3e7c125264ca8b909e693841210534b1ea1752f/pyright-1.1.402.tar.gz", hash = "sha256:85a33c2d40cd4439c66aa946fd4ce71ab2f3f5b8c22ce36a623f59ac22937683", size = 3888207, upload-time = "2025-06-11T08:48:35.759Z" }
|
| 1099 |
wheels = [
|
| 1100 |
+
{ url = "https://files.pythonhosted.org/packages/fe/37/1a1c62d955e82adae588be8e374c7f77b165b6cb4203f7d581269959abbc/pyright-1.1.402-py3-none-any.whl", hash = "sha256:2c721f11869baac1884e846232800fe021c33f1b4acb3929cff321f7ea4e2982", size = 5624004, upload-time = "2025-06-11T08:48:33.998Z" },
|
| 1101 |
]
|
| 1102 |
|
| 1103 |
[[package]]
|
| 1104 |
name = "pytest"
|
| 1105 |
+
version = "8.4.0"
|
| 1106 |
source = { registry = "https://pypi.org/simple" }
|
| 1107 |
dependencies = [
|
| 1108 |
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
|
|
| 1110 |
{ name = "iniconfig" },
|
| 1111 |
{ name = "packaging" },
|
| 1112 |
{ name = "pluggy" },
|
| 1113 |
+
{ name = "pygments" },
|
| 1114 |
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
| 1115 |
]
|
| 1116 |
+
sdist = { url = "https://files.pythonhosted.org/packages/fb/aa/405082ce2749be5398045152251ac69c0f3578c7077efc53431303af97ce/pytest-8.4.0.tar.gz", hash = "sha256:14d920b48472ea0dbf68e45b96cd1ffda4705f33307dcc86c676c1b5104838a6", size = 1515232, upload-time = "2025-06-02T17:36:30.03Z" }
|
| 1117 |
wheels = [
|
| 1118 |
+
{ url = "https://files.pythonhosted.org/packages/2f/de/afa024cbe022b1b318a3d224125aa24939e99b4ff6f22e0ba639a2eaee47/pytest-8.4.0-py3-none-any.whl", hash = "sha256:f40f825768ad76c0977cbacdf1fd37c6f7a468e460ea6a0636078f8972d4517e", size = 363797, upload-time = "2025-06-02T17:36:27.859Z" },
|
| 1119 |
]
|
| 1120 |
|
| 1121 |
[[package]]
|
|
|
|
| 1132 |
|
| 1133 |
[[package]]
|
| 1134 |
name = "pytest-cov"
|
| 1135 |
+
version = "6.2.1"
|
| 1136 |
source = { registry = "https://pypi.org/simple" }
|
| 1137 |
dependencies = [
|
| 1138 |
{ name = "coverage", extra = ["toml"] },
|
| 1139 |
+
{ name = "pluggy" },
|
| 1140 |
{ name = "pytest" },
|
| 1141 |
]
|
| 1142 |
+
sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" }
|
| 1143 |
wheels = [
|
| 1144 |
+
{ url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" },
|
| 1145 |
]
|
| 1146 |
|
| 1147 |
[[package]]
|
|
|
|
| 1349 |
|
| 1350 |
[[package]]
|
| 1351 |
name = "requests"
|
| 1352 |
+
version = "2.32.4"
|
| 1353 |
source = { registry = "https://pypi.org/simple" }
|
| 1354 |
dependencies = [
|
| 1355 |
{ name = "certifi" },
|
|
|
|
| 1357 |
{ name = "idna" },
|
| 1358 |
{ name = "urllib3" },
|
| 1359 |
]
|
| 1360 |
+
sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" }
|
| 1361 |
wheels = [
|
| 1362 |
+
{ url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" },
|
| 1363 |
]
|
| 1364 |
|
| 1365 |
[[package]]
|
|
|
|
| 1378 |
|
| 1379 |
[[package]]
|
| 1380 |
name = "ruff"
|
| 1381 |
+
version = "0.11.13"
|
| 1382 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1383 |
+
sdist = { url = "https://files.pythonhosted.org/packages/ed/da/9c6f995903b4d9474b39da91d2d626659af3ff1eeb43e9ae7c119349dba6/ruff-0.11.13.tar.gz", hash = "sha256:26fa247dc68d1d4e72c179e08889a25ac0c7ba4d78aecfc835d49cbfd60bf514", size = 4282054, upload-time = "2025-06-05T21:00:15.721Z" }
|
| 1384 |
+
wheels = [
|
| 1385 |
+
{ url = "https://files.pythonhosted.org/packages/7d/ce/a11d381192966e0b4290842cc8d4fac7dc9214ddf627c11c1afff87da29b/ruff-0.11.13-py3-none-linux_armv6l.whl", hash = "sha256:4bdfbf1240533f40042ec00c9e09a3aade6f8c10b6414cf11b519488d2635d46", size = 10292516, upload-time = "2025-06-05T20:59:32.944Z" },
|
| 1386 |
+
{ url = "https://files.pythonhosted.org/packages/78/db/87c3b59b0d4e753e40b6a3b4a2642dfd1dcaefbff121ddc64d6c8b47ba00/ruff-0.11.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aef9c9ed1b5ca28bb15c7eac83b8670cf3b20b478195bd49c8d756ba0a36cf48", size = 11106083, upload-time = "2025-06-05T20:59:37.03Z" },
|
| 1387 |
+
{ url = "https://files.pythonhosted.org/packages/77/79/d8cec175856ff810a19825d09ce700265f905c643c69f45d2b737e4a470a/ruff-0.11.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53b15a9dfdce029c842e9a5aebc3855e9ab7771395979ff85b7c1dedb53ddc2b", size = 10436024, upload-time = "2025-06-05T20:59:39.741Z" },
|
| 1388 |
+
{ url = "https://files.pythonhosted.org/packages/8b/5b/f6d94f2980fa1ee854b41568368a2e1252681b9238ab2895e133d303538f/ruff-0.11.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab153241400789138d13f362c43f7edecc0edfffce2afa6a68434000ecd8f69a", size = 10646324, upload-time = "2025-06-05T20:59:42.185Z" },
|
| 1389 |
+
{ url = "https://files.pythonhosted.org/packages/6c/9c/b4c2acf24ea4426016d511dfdc787f4ce1ceb835f3c5fbdbcb32b1c63bda/ruff-0.11.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c51f93029d54a910d3d24f7dd0bb909e31b6cd989a5e4ac513f4eb41629f0dc", size = 10174416, upload-time = "2025-06-05T20:59:44.319Z" },
|
| 1390 |
+
{ url = "https://files.pythonhosted.org/packages/f3/10/e2e62f77c65ede8cd032c2ca39c41f48feabedb6e282bfd6073d81bb671d/ruff-0.11.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1808b3ed53e1a777c2ef733aca9051dc9bf7c99b26ece15cb59a0320fbdbd629", size = 11724197, upload-time = "2025-06-05T20:59:46.935Z" },
|
| 1391 |
+
{ url = "https://files.pythonhosted.org/packages/bb/f0/466fe8469b85c561e081d798c45f8a1d21e0b4a5ef795a1d7f1a9a9ec182/ruff-0.11.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d28ce58b5ecf0f43c1b71edffabe6ed7f245d5336b17805803312ec9bc665933", size = 12511615, upload-time = "2025-06-05T20:59:49.534Z" },
|
| 1392 |
+
{ url = "https://files.pythonhosted.org/packages/17/0e/cefe778b46dbd0cbcb03a839946c8f80a06f7968eb298aa4d1a4293f3448/ruff-0.11.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55e4bc3a77842da33c16d55b32c6cac1ec5fb0fbec9c8c513bdce76c4f922165", size = 12117080, upload-time = "2025-06-05T20:59:51.654Z" },
|
| 1393 |
+
{ url = "https://files.pythonhosted.org/packages/5d/2c/caaeda564cbe103bed145ea557cb86795b18651b0f6b3ff6a10e84e5a33f/ruff-0.11.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:633bf2c6f35678c56ec73189ba6fa19ff1c5e4807a78bf60ef487b9dd272cc71", size = 11326315, upload-time = "2025-06-05T20:59:54.469Z" },
|
| 1394 |
+
{ url = "https://files.pythonhosted.org/packages/75/f0/782e7d681d660eda8c536962920c41309e6dd4ebcea9a2714ed5127d44bd/ruff-0.11.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ffbc82d70424b275b089166310448051afdc6e914fdab90e08df66c43bb5ca9", size = 11555640, upload-time = "2025-06-05T20:59:56.986Z" },
|
| 1395 |
+
{ url = "https://files.pythonhosted.org/packages/5d/d4/3d580c616316c7f07fb3c99dbecfe01fbaea7b6fd9a82b801e72e5de742a/ruff-0.11.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4a9ddd3ec62a9a89578c85842b836e4ac832d4a2e0bfaad3b02243f930ceafcc", size = 10507364, upload-time = "2025-06-05T20:59:59.154Z" },
|
| 1396 |
+
{ url = "https://files.pythonhosted.org/packages/5a/dc/195e6f17d7b3ea6b12dc4f3e9de575db7983db187c378d44606e5d503319/ruff-0.11.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d237a496e0778d719efb05058c64d28b757c77824e04ffe8796c7436e26712b7", size = 10141462, upload-time = "2025-06-05T21:00:01.481Z" },
|
| 1397 |
+
{ url = "https://files.pythonhosted.org/packages/f4/8e/39a094af6967faa57ecdeacb91bedfb232474ff8c3d20f16a5514e6b3534/ruff-0.11.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:26816a218ca6ef02142343fd24c70f7cd8c5aa6c203bca284407adf675984432", size = 11121028, upload-time = "2025-06-05T21:00:04.06Z" },
|
| 1398 |
+
{ url = "https://files.pythonhosted.org/packages/5a/c0/b0b508193b0e8a1654ec683ebab18d309861f8bd64e3a2f9648b80d392cb/ruff-0.11.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:51c3f95abd9331dc5b87c47ac7f376db5616041173826dfd556cfe3d4977f492", size = 11602992, upload-time = "2025-06-05T21:00:06.249Z" },
|
| 1399 |
+
{ url = "https://files.pythonhosted.org/packages/7c/91/263e33ab93ab09ca06ce4f8f8547a858cc198072f873ebc9be7466790bae/ruff-0.11.13-py3-none-win32.whl", hash = "sha256:96c27935418e4e8e77a26bb05962817f28b8ef3843a6c6cc49d8783b5507f250", size = 10474944, upload-time = "2025-06-05T21:00:08.459Z" },
|
| 1400 |
+
{ url = "https://files.pythonhosted.org/packages/46/f4/7c27734ac2073aae8efb0119cae6931b6fb48017adf048fdf85c19337afc/ruff-0.11.13-py3-none-win_amd64.whl", hash = "sha256:29c3189895a8a6a657b7af4e97d330c8a3afd2c9c8f46c81e2fc5a31866517e3", size = 11548669, upload-time = "2025-06-05T21:00:11.147Z" },
|
| 1401 |
+
{ url = "https://files.pythonhosted.org/packages/ec/bf/b273dd11673fed8a6bd46032c0ea2a04b2ac9bfa9c628756a5856ba113b0/ruff-0.11.13-py3-none-win_arm64.whl", hash = "sha256:b4385285e9179d608ff1d2fb9922062663c658605819a6876d8beef0c30b7f3b", size = 10683928, upload-time = "2025-06-05T21:00:13.758Z" },
|
| 1402 |
]
|
| 1403 |
|
| 1404 |
[[package]]
|
|
|
|
| 1567 |
|
| 1568 |
[[package]]
|
| 1569 |
name = "typing-extensions"
|
| 1570 |
+
version = "4.14.0"
|
| 1571 |
source = { registry = "https://pypi.org/simple" }
|
| 1572 |
+
sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" }
|
| 1573 |
wheels = [
|
| 1574 |
+
{ url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" },
|
| 1575 |
]
|
| 1576 |
|
| 1577 |
[[package]]
|
|
|
|
| 1597 |
|
| 1598 |
[[package]]
|
| 1599 |
name = "uvicorn"
|
| 1600 |
+
version = "0.34.3"
|
| 1601 |
source = { registry = "https://pypi.org/simple" }
|
| 1602 |
dependencies = [
|
| 1603 |
{ name = "click" },
|
| 1604 |
{ name = "h11" },
|
| 1605 |
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
| 1606 |
]
|
| 1607 |
+
sdist = { url = "https://files.pythonhosted.org/packages/de/ad/713be230bcda622eaa35c28f0d328c3675c371238470abdea52417f17a8e/uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a", size = 76631, upload-time = "2025-06-01T07:48:17.531Z" }
|
| 1608 |
wheels = [
|
| 1609 |
+
{ url = "https://files.pythonhosted.org/packages/6d/0d/8adfeaa62945f90d19ddc461c55f4a50c258af7662d34b6a3d5d1f8646f6/uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885", size = 62431, upload-time = "2025-06-01T07:48:15.664Z" },
|
| 1610 |
]
|
| 1611 |
|
| 1612 |
[[package]]
|