mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-22 13:34:17 +02:00
Merge main into 2-14-deprecations
Resolved conflicts: - docs/docs.json: Kept debug provider, removed bearer provider - docs/python-sdk/fastmcp-server-server.mdx: Removed run_streamable_http_async
This commit is contained in:
commit
897ba21ca2
83 changed files with 1589 additions and 584 deletions
9
.github/workflows/marvin.yml
vendored
9
.github/workflows/marvin.yml
vendored
|
|
@ -44,11 +44,10 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: uv sync --python 3.12
|
||||
|
||||
# Install pre-commit hooks automatically
|
||||
- name: Install pre-commit hooks
|
||||
run: |
|
||||
uv run pre-commit install
|
||||
echo "✅ Pre-commit hooks installed"
|
||||
- name: Run prek
|
||||
uses: j178/prek-action@v1
|
||||
env:
|
||||
SKIP: no-commit-to-branch
|
||||
|
||||
- name: Generate Marvin App token
|
||||
id: marvin-token
|
||||
|
|
|
|||
4
.github/workflows/run-static.yml
vendored
4
.github/workflows/run-static.yml
vendored
|
|
@ -48,7 +48,7 @@ jobs:
|
|||
exit 1
|
||||
fi
|
||||
echo "✅ Lockfile is up to date"
|
||||
- name: Run pre-commit
|
||||
run: uv run pre-commit run --all-files
|
||||
- name: Run prek
|
||||
uses: j178/prek-action@v1
|
||||
env:
|
||||
SKIP: no-commit-to-branch
|
||||
|
|
|
|||
4
.github/workflows/run-tests.yml
vendored
4
.github/workflows/run-tests.yml
vendored
|
|
@ -79,10 +79,10 @@ jobs:
|
|||
run: uv sync --resolution lowest-direct
|
||||
|
||||
- name: Run tests (excluding integration and client_process)
|
||||
run: uv run pytest --inline-snapshot=disable tests -m "not integration and not client_process" --numprocesses auto --maxprocesses 4 --dist worksteal
|
||||
run: uv run --resolution lowest-direct pytest --inline-snapshot=disable tests -m "not integration and not client_process" --numprocesses auto --maxprocesses 4 --dist worksteal
|
||||
|
||||
- name: Run client process tests separately
|
||||
run: uv run pytest --inline-snapshot=disable tests -m "client_process" -x
|
||||
run: uv run --resolution lowest-direct pytest --inline-snapshot=disable tests -m "client_process" -x
|
||||
|
||||
run_integration_tests:
|
||||
name: "Run integration tests"
|
||||
|
|
|
|||
14
AGENTS.md
14
AGENTS.md
|
|
@ -10,7 +10,7 @@ FastMCP is a comprehensive Python framework (Python ≥3.10) for building Model
|
|||
|
||||
```bash
|
||||
uv sync # Install dependencies
|
||||
uv run pre-commit run --all-files # Ruff + Prettier + ty
|
||||
uv run prek run --all-files # Ruff + Prettier + ty
|
||||
uv run pytest # Run full test suite
|
||||
```
|
||||
|
||||
|
|
@ -100,12 +100,12 @@ async with Client(transport=StreamableHttpTransport(server_url)) as client:
|
|||
|
||||
### Git & CI
|
||||
|
||||
- Pre-commit hooks are required (run automatically on commits)
|
||||
- Never amend commits to fix pre-commit failures
|
||||
- Prek hooks are required (run automatically on commits)
|
||||
- Never amend commits to fix prek failures
|
||||
- Apply PR labels: bugs/breaking/enhancements/features
|
||||
- Improvements = enhancements (not features) unless specified
|
||||
- **NEVER** force-push on collaborative repos
|
||||
- **ALWAYS** run pre-commit before PRs
|
||||
- **ALWAYS** run prek before PRs
|
||||
|
||||
### Commit Messages and Agent Attribution
|
||||
|
||||
|
|
@ -217,7 +217,7 @@ If something needs work, your review should help it get there through specific,
|
|||
|
||||
Before approving, verify:
|
||||
|
||||
- [ ] All required development workflow steps completed (uv sync, pre-commit, pytest)
|
||||
- [ ] All required development workflow steps completed (uv sync, prek, pytest)
|
||||
- [ ] Changes align with repository patterns and conventions
|
||||
- [ ] API changes are documented and backwards-compatible where possible
|
||||
- [ ] Error handling follows project patterns (specific exception types)
|
||||
|
|
@ -237,7 +237,7 @@ uv sync # Installs all deps including dev tools
|
|||
|
||||
- **Linting**: `uv run ruff check` (or with `--fix`)
|
||||
- **Type Checking**: `uv run ty check`
|
||||
- **All Checks**: `uv run pre-commit run --all-files`
|
||||
- **All Checks**: `uv run prek run --all-files`
|
||||
|
||||
### Testing
|
||||
|
||||
|
|
@ -260,6 +260,6 @@ uv sync # Installs all deps including dev tools
|
|||
### Build Issues (Common Solutions)
|
||||
|
||||
1. **Dependencies**: Always `uv sync` first
|
||||
2. **Pre-commit fails**: Run `uv run pre-commit run --all-files` to see failures
|
||||
2. **Prek fails**: Run `uv run prek run --all-files` to see failures
|
||||
3. **Type errors**: Use `uv run ty check` directly, check `pyproject.toml` config
|
||||
4. **Test timeouts**: Default 5s - optimize or mark as integration tests
|
||||
|
|
|
|||
12
README.md
12
README.md
|
|
@ -143,6 +143,8 @@ uv pip install fastmcp
|
|||
|
||||
For full installation instructions, including verification, upgrading from the official MCPSDK, and developer setup, see the [**Installation Guide**](https://gofastmcp.com/getting-started/installation).
|
||||
|
||||
**Dependency Licensing:** FastMCP depends on Cyclopts for CLI functionality. Cyclopts v4 includes docutils as a transitive dependency, which has complex licensing that may trigger compliance reviews in some organizations. If this is a concern, you can install Cyclopts v5 alpha (`pip install "cyclopts>=5.0.0a1"`) which removes this dependency, or wait for the stable v5 release. See [this issue](https://github.com/BrianPugh/cyclopts/issues/672) for details.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
These are the building blocks for creating MCP servers and clients with FastMCP.
|
||||
|
|
@ -481,20 +483,20 @@ uv run pytest --cov=src --cov=examples --cov-report=html
|
|||
|
||||
### Static Checks
|
||||
|
||||
FastMCP uses `pre-commit` for code formatting, linting, and type-checking. All PRs must pass these checks (they run automatically in CI).
|
||||
FastMCP uses `prek` for code formatting, linting, and type-checking. All PRs must pass these checks (they run automatically in CI).
|
||||
|
||||
Install the hooks locally:
|
||||
|
||||
```bash
|
||||
uv run pre-commit install
|
||||
uv run prek install
|
||||
```
|
||||
|
||||
The hooks will now run automatically on `git commit`. You can also run them manually at any time:
|
||||
|
||||
```bash
|
||||
pre-commit run --all-files
|
||||
prek run --all-files
|
||||
# or via uv
|
||||
uv run pre-commit run --all-files
|
||||
uv run prek run --all-files
|
||||
```
|
||||
|
||||
### Pull Requests
|
||||
|
|
@ -502,7 +504,7 @@ uv run pre-commit run --all-files
|
|||
1. Fork the repository on GitHub.
|
||||
2. Create a feature branch from `main`.
|
||||
3. Make your changes, including tests and documentation updates.
|
||||
4. Ensure tests and pre-commit hooks pass.
|
||||
4. Ensure tests and prek hooks pass.
|
||||
5. Commit your changes and push to your fork.
|
||||
6. Open a pull request against the `main` branch of `jlowin/fastmcp`.
|
||||
|
||||
|
|
|
|||
|
|
@ -223,6 +223,55 @@ async with client:
|
|||
print("Server is reachable")
|
||||
```
|
||||
|
||||
### Initialization and Server Information
|
||||
|
||||
When you enter the client context manager, the client automatically performs an MCP initialization handshake with the server. This handshake exchanges capabilities, server metadata, and instructions. The result is available through the `initialize_result` property.
|
||||
|
||||
```python
|
||||
from fastmcp import Client, FastMCP
|
||||
|
||||
mcp = FastMCP(name="MyServer", instructions="Use the greet tool to say hello!")
|
||||
|
||||
@mcp.tool
|
||||
def greet(name: str) -> str:
|
||||
"""Greet a user by name."""
|
||||
return f"Hello, {name}!"
|
||||
|
||||
async with Client(mcp) as client:
|
||||
# Initialization already happened automatically
|
||||
print(f"Server: {client.initialize_result.serverInfo.name}")
|
||||
print(f"Version: {client.initialize_result.serverInfo.version}")
|
||||
print(f"Instructions: {client.initialize_result.instructions}")
|
||||
print(f"Capabilities: {client.initialize_result.capabilities.tools}")
|
||||
```
|
||||
|
||||
#### Manual Initialization Control
|
||||
|
||||
In advanced scenarios, you might want precise control over when initialization happens. For example, you may need custom error handling, want to defer initialization until after other setup, or need to measure initialization timing separately.
|
||||
|
||||
Disable automatic initialization and call `initialize()` manually:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
||||
# Disable automatic initialization
|
||||
client = Client("my_mcp_server.py", auto_initialize=False)
|
||||
|
||||
async with client:
|
||||
# Connection established, but not initialized yet
|
||||
print(f"Connected: {client.is_connected()}")
|
||||
print(f"Initialized: {client.initialize_result is not None}") # False
|
||||
|
||||
# Initialize manually with custom timeout
|
||||
result = await client.initialize(timeout=10.0)
|
||||
print(f"Server: {result.serverInfo.name}")
|
||||
|
||||
# Now ready for operations
|
||||
tools = await client.list_tools()
|
||||
```
|
||||
|
||||
The `initialize()` method is idempotent - calling it multiple times returns the cached result from the first successful call.
|
||||
|
||||
## Client Configuration
|
||||
|
||||
Clients can be configured with additional handlers and settings for specialized use cases.
|
||||
|
|
|
|||
|
|
@ -101,6 +101,31 @@ async with client:
|
|||
- `arguments`: Dictionary of arguments to pass to the tool (optional)
|
||||
- `timeout`: Maximum execution time in seconds (optional, overrides client-level timeout)
|
||||
- `progress_handler`: Progress callback function (optional, overrides client-level handler)
|
||||
- `meta`: Dictionary of metadata to send with the request (optional, see below)
|
||||
|
||||
## Sending Metadata
|
||||
|
||||
<VersionBadge version="2.13.1" />
|
||||
|
||||
The `meta` parameter sends ancillary information alongside tool calls. This can be used for various purposes like observability, debugging, client identification, or any context the server may need beyond the tool's primary arguments.
|
||||
|
||||
```python
|
||||
async with client:
|
||||
result = await client.call_tool(
|
||||
name="send_email",
|
||||
arguments={
|
||||
"to": "user@example.com",
|
||||
"subject": "Hello",
|
||||
"body": "Welcome!"
|
||||
},
|
||||
meta={
|
||||
"trace_id": "abc-123",
|
||||
"request_source": "mobile_app"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The structure and usage of `meta` is determined by your application. See [Client Metadata](/servers/context#client-metadata) in the server documentation to learn how to access this data in your tool implementations.
|
||||
|
||||
## Handling Results
|
||||
|
||||
|
|
|
|||
|
|
@ -49,13 +49,13 @@ cd fastmcp
|
|||
# Install all dependencies including dev tools
|
||||
uv sync
|
||||
|
||||
# Install pre-commit hooks
|
||||
uv run pre-commit install
|
||||
# Install prek hooks
|
||||
uv run prek install
|
||||
```
|
||||
|
||||
In addition, some development commands require [just](https://github.com/casey/just) to be installed.
|
||||
|
||||
Pre-commit hooks will run automatically on every commit to catch issues before they reach CI. If you see failures, fix them before committing - never commit broken code expecting to fix it later.
|
||||
Prek hooks will run automatically on every commit to catch issues before they reach CI. If you see failures, fix them before committing - never commit broken code expecting to fix it later.
|
||||
|
||||
### Development Standards
|
||||
|
||||
|
|
@ -100,19 +100,19 @@ The focus is on idiomatic, high-quality Python. FastMCP uses patterns like `NotS
|
|||
|
||||
**Breaking established patterns** confuses readers. If you must deviate, discuss in the issue first.
|
||||
|
||||
### Pre-Commit Checks
|
||||
### Prek Checks
|
||||
|
||||
```bash
|
||||
# Runs automatically on commit, or manually:
|
||||
uv run pre-commit run --all-files
|
||||
uv run prek run --all-files
|
||||
```
|
||||
|
||||
This runs three critical tools:
|
||||
- **Ruff**: Linting and formatting
|
||||
- **ty**: Static type checking
|
||||
- **Pytest**: Core test suite
|
||||
- **Prettier**: Code formatting
|
||||
- **ty**: Static type checking
|
||||
|
||||
CI will reject PRs that fail these checks. Always run them locally first.
|
||||
Pytest runs separately as a distinct workflow step after prek checks pass. CI will reject PRs that fail these checks. Always run them locally first.
|
||||
|
||||
### Testing
|
||||
|
||||
|
|
@ -155,7 +155,7 @@ just api-ref-all
|
|||
|
||||
#### Before Submitting
|
||||
|
||||
1. **Run all checks**: `uv run pre-commit run --all-files && uv run pytest`
|
||||
1. **Run all checks**: `uv run prek run --all-files && uv run pytest`
|
||||
2. **Keep scope small**: One feature or fix per PR
|
||||
3. **Write clear description**: Your PR description becomes permanent documentation
|
||||
4. **Update docs**: Include documentation for API changes
|
||||
|
|
|
|||
|
|
@ -355,6 +355,7 @@
|
|||
"python-sdk/fastmcp-server-auth-providers-auth0",
|
||||
"python-sdk/fastmcp-server-auth-providers-aws",
|
||||
"python-sdk/fastmcp-server-auth-providers-azure",
|
||||
"python-sdk/fastmcp-server-auth-providers-debug",
|
||||
"python-sdk/fastmcp-server-auth-providers-descope",
|
||||
"python-sdk/fastmcp-server-auth-providers-github",
|
||||
"python-sdk/fastmcp-server-auth-providers-google",
|
||||
|
|
|
|||
|
|
@ -42,6 +42,20 @@ Python version: 3.12.2
|
|||
Platform: macOS-15.3.1-arm64-arm-64bit
|
||||
FastMCP root path: ~/Developer/fastmcp
|
||||
```
|
||||
|
||||
### Dependency Licensing
|
||||
|
||||
<Info>
|
||||
FastMCP depends on Cyclopts for CLI functionality. Cyclopts v4 includes docutils as a transitive dependency, which has complex licensing that may trigger compliance reviews in some organizations.
|
||||
|
||||
If this is a concern, you can install Cyclopts v5 alpha which removes this dependency:
|
||||
|
||||
```bash
|
||||
pip install "cyclopts>=5.0.0a1"
|
||||
```
|
||||
|
||||
Alternatively, wait for the stable v5 release. See [this issue](https://github.com/BrianPugh/cyclopts/issues/672) for details.
|
||||
</Info>
|
||||
## Upgrading from the Official MCP SDK
|
||||
|
||||
Upgrading from the official MCP SDK's FastMCP 1.0 to FastMCP 2.0 is generally straightforward. The core server API is highly compatible, and in many cases, changing your import statement from `from mcp.server.fastmcp import FastMCP` to `from fastmcp import FastMCP` will be sufficient.
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ fastmcp inspect # auto-detect fastmcp.json
|
|||
- `server_spec`: Python file to inspect, optionally with \:object suffix, or fastmcp.json
|
||||
|
||||
|
||||
### `prepare` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L786" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `prepare` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L785" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
prepare(config_path: Annotated[str | None, cyclopts.Parameter(help='Path to fastmcp.json configuration file')] = None, output_dir: Annotated[str | None, cyclopts.Parameter(help='Directory to create the persistent environment in')] = None, skip_source: Annotated[bool, cyclopts.Parameter(help='Skip source preparation (e.g., git clone)')] = False) -> None
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Cursor integration for FastMCP install using Cyclopts.
|
|||
|
||||
## Functions
|
||||
|
||||
### `generate_cursor_deeplink` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L21" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `generate_cursor_deeplink` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L23" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
generate_cursor_deeplink(server_name: str, server_config: StdioMCPServer) -> str
|
||||
|
|
@ -27,7 +27,7 @@ Generate a Cursor deeplink for installing the MCP server.
|
|||
- Deeplink URL that can be clicked to install the server
|
||||
|
||||
|
||||
### `open_deeplink` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L45" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `open_deeplink` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L47" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
open_deeplink(deeplink: str) -> bool
|
||||
|
|
@ -43,7 +43,7 @@ Attempt to open a deeplink URL using the system's default handler.
|
|||
- True if the command succeeded, False otherwise
|
||||
|
||||
|
||||
### `install_cursor_workspace` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L68" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `install_cursor_workspace` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L73" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
install_cursor_workspace(file: Path, server_object: str | None, name: str, workspace_path: Path) -> bool
|
||||
|
|
@ -68,7 +68,7 @@ Install FastMCP server to workspace-specific Cursor configuration.
|
|||
- True if installation was successful, False otherwise
|
||||
|
||||
|
||||
### `install_cursor` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L149" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `install_cursor` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L154" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
install_cursor(file: Path, server_object: str | None, name: str) -> bool
|
||||
|
|
@ -93,7 +93,7 @@ Install FastMCP server in Cursor.
|
|||
- True if installation was successful, False otherwise
|
||||
|
||||
|
||||
### `cursor_command` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L234" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `cursor_command` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/cursor.py#L239" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
cursor_command(server_spec: str) -> None
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: oauth
|
|||
|
||||
## Functions
|
||||
|
||||
### `check_if_auth_required` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L42" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `check_if_auth_required` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L40" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
check_if_auth_required(mcp_url: str, httpx_kwargs: dict[str, Any] | None = None) -> bool
|
||||
|
|
@ -28,41 +28,41 @@ Check if the MCP endpoint requires authentication by making a test request.
|
|||
Raised when OAuth client credentials are not found on the server.
|
||||
|
||||
|
||||
### `TokenStorageAdapter` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L72" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `TokenStorageAdapter` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L70" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `clear` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L100" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `clear` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L98" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
clear(self) -> None
|
||||
```
|
||||
|
||||
#### `get_tokens` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L105" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_tokens` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L103" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_tokens(self) -> OAuthToken | None
|
||||
```
|
||||
|
||||
#### `set_tokens` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L109" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_tokens` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L107" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_tokens(self, tokens: OAuthToken) -> None
|
||||
```
|
||||
|
||||
#### `get_client_info` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L117" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_client_info` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L115" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_client_info(self) -> OAuthClientInformationFull | None
|
||||
```
|
||||
|
||||
#### `set_client_info` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L123" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_client_info` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L121" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_client_info(self, client_info: OAuthClientInformationFull) -> None
|
||||
```
|
||||
|
||||
### `OAuth` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L136" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `OAuth` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L134" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
OAuth client provider for MCP servers with browser-based authentication.
|
||||
|
|
@ -91,7 +91,7 @@ callback_handler(self) -> tuple[str, str | None]
|
|||
Handle OAuth callback and return (auth_code, state).
|
||||
|
||||
|
||||
#### `async_auth_flow` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L284" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `async_auth_flow` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/auth/oauth.py#L286" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
async_auth_flow(self, request: httpx.Request) -> AsyncGenerator[httpx.Request, httpx.Response]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: client
|
|||
|
||||
## Classes
|
||||
|
||||
### `ClientSessionState` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L81" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ClientSessionState` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L91" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Holds all session-related state for a Client instance.
|
||||
|
|
@ -16,7 +16,7 @@ This allows clean separation of configuration (which is copied) from
|
|||
session state (which should be fresh for each new client instance).
|
||||
|
||||
|
||||
### `Client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L97" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `Client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L107" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
MCP client that delegates connection management to a Transport instance.
|
||||
|
|
@ -79,7 +79,7 @@ async with client:
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L283" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L291" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
session(self) -> ClientSession
|
||||
|
|
@ -88,16 +88,16 @@ session(self) -> ClientSession
|
|||
Get the current active session. Raises RuntimeError if not connected.
|
||||
|
||||
|
||||
#### `initialize_result` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L293" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `initialize_result` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L301" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
initialize_result(self) -> mcp.types.InitializeResult
|
||||
initialize_result(self) -> mcp.types.InitializeResult | None
|
||||
```
|
||||
|
||||
Get the result of the initialization request.
|
||||
|
||||
|
||||
#### `set_roots` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L301" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_roots` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L305" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_roots(self, roots: RootsList | RootsHandler) -> None
|
||||
|
|
@ -106,7 +106,7 @@ set_roots(self, roots: RootsList | RootsHandler) -> None
|
|||
Set the roots for the client. This does not automatically call `send_roots_list_changed`.
|
||||
|
||||
|
||||
#### `set_sampling_callback` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L305" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_sampling_callback` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L309" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_sampling_callback(self, sampling_callback: ClientSamplingHandler) -> None
|
||||
|
|
@ -115,7 +115,7 @@ set_sampling_callback(self, sampling_callback: ClientSamplingHandler) -> None
|
|||
Set the sampling callback for the client.
|
||||
|
||||
|
||||
#### `set_elicitation_callback` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L311" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_elicitation_callback` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L315" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_elicitation_callback(self, elicitation_callback: ElicitationHandler) -> None
|
||||
|
|
@ -124,7 +124,7 @@ set_elicitation_callback(self, elicitation_callback: ElicitationHandler) -> None
|
|||
Set the elicitation callback for the client.
|
||||
|
||||
|
||||
#### `is_connected` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L319" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `is_connected` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L323" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
is_connected(self) -> bool
|
||||
|
|
@ -133,7 +133,7 @@ is_connected(self) -> bool
|
|||
Check if the client is currently connected.
|
||||
|
||||
|
||||
#### `new` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L323" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `new` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L327" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
new(self) -> Client[ClientTransportT]
|
||||
|
|
@ -155,7 +155,35 @@ share state with the original client.
|
|||
close(self)
|
||||
```
|
||||
|
||||
#### `ping` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L496" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `initialize` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L496" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
initialize(self, timeout: datetime.timedelta | float | int | None = None) -> mcp.types.InitializeResult
|
||||
```
|
||||
|
||||
Send an initialize request to the server.
|
||||
|
||||
This method performs the MCP initialization handshake with the server,
|
||||
exchanging capabilities and server information. It is idempotent - calling
|
||||
it multiple times returns the cached result from the first call.
|
||||
|
||||
The initialization happens automatically when entering the client context
|
||||
manager unless `auto_initialize=False` was set during client construction.
|
||||
Manual calls to this method are only needed when auto-initialization is disabled.
|
||||
|
||||
**Args:**
|
||||
- `timeout`: Optional timeout for the initialization request (seconds or timedelta).
|
||||
If None, uses the client's init_timeout setting.
|
||||
|
||||
**Returns:**
|
||||
- The server's initialization response containing server info,
|
||||
capabilities, protocol version, and optional instructions.
|
||||
|
||||
**Raises:**
|
||||
- `RuntimeError`: If the client is not connected or initialization times out.
|
||||
|
||||
|
||||
#### `ping` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L545" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
ping(self) -> bool
|
||||
|
|
@ -164,7 +192,7 @@ ping(self) -> bool
|
|||
Send a ping request.
|
||||
|
||||
|
||||
#### `cancel` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L501" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `cancel` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L550" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
cancel(self, request_id: str | int, reason: str | None = None) -> None
|
||||
|
|
@ -173,7 +201,7 @@ cancel(self, request_id: str | int, reason: str | None = None) -> None
|
|||
Send a cancellation notification for an in-progress request.
|
||||
|
||||
|
||||
#### `progress` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L518" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `progress` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L567" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
progress(self, progress_token: str | int, progress: float, total: float | None = None, message: str | None = None) -> None
|
||||
|
|
@ -182,7 +210,7 @@ progress(self, progress_token: str | int, progress: float, total: float | None =
|
|||
Send a progress notification.
|
||||
|
||||
|
||||
#### `set_logging_level` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L530" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_logging_level` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L579" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_logging_level(self, level: mcp.types.LoggingLevel) -> None
|
||||
|
|
@ -191,7 +219,7 @@ set_logging_level(self, level: mcp.types.LoggingLevel) -> None
|
|||
Send a logging/setLevel request.
|
||||
|
||||
|
||||
#### `send_roots_list_changed` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L534" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `send_roots_list_changed` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L583" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
send_roots_list_changed(self) -> None
|
||||
|
|
@ -200,7 +228,7 @@ send_roots_list_changed(self) -> None
|
|||
Send a roots/list_changed notification.
|
||||
|
||||
|
||||
#### `list_resources_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L540" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_resources_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L589" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_resources_mcp(self) -> mcp.types.ListResourcesResult
|
||||
|
|
@ -216,7 +244,7 @@ containing the list of resources and any additional metadata.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `list_resources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L555" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_resources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L604" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_resources(self) -> list[mcp.types.Resource]
|
||||
|
|
@ -231,7 +259,7 @@ Retrieve a list of resources available on the server.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `list_resource_templates_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L567" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_resource_templates_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L616" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_resource_templates_mcp(self) -> mcp.types.ListResourceTemplatesResult
|
||||
|
|
@ -247,7 +275,7 @@ containing the list of resource templates and any additional metadata.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `list_resource_templates` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L584" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_resource_templates` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L633" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_resource_templates(self) -> list[mcp.types.ResourceTemplate]
|
||||
|
|
@ -262,7 +290,7 @@ Retrieve a list of resource templates available on the server.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `read_resource_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L598" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `read_resource_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L647" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
read_resource_mcp(self, uri: AnyUrl | str) -> mcp.types.ReadResourceResult
|
||||
|
|
@ -281,7 +309,7 @@ containing the resource contents and any additional metadata.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `read_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L620" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `read_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L669" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
read_resource(self, uri: AnyUrl | str) -> list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents]
|
||||
|
|
@ -300,7 +328,7 @@ objects, typically containing either text or binary data.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `list_prompts_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L659" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_prompts_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L708" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_prompts_mcp(self) -> mcp.types.ListPromptsResult
|
||||
|
|
@ -316,7 +344,7 @@ containing the list of prompts and any additional metadata.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `list_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L674" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L723" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_prompts(self) -> list[mcp.types.Prompt]
|
||||
|
|
@ -331,7 +359,7 @@ Retrieve a list of prompts available on the server.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `get_prompt_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L687" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_prompt_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L736" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_prompt_mcp(self, name: str, arguments: dict[str, Any] | None = None) -> mcp.types.GetPromptResult
|
||||
|
|
@ -351,7 +379,7 @@ containing the prompt messages and any additional metadata.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `get_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L723" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L772" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> mcp.types.GetPromptResult
|
||||
|
|
@ -371,7 +399,7 @@ containing the prompt messages and any additional metadata.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `complete_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L744" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `complete_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L793" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
complete_mcp(self, ref: mcp.types.ResourceTemplateReference | mcp.types.PromptReference, argument: dict[str, str], context_arguments: dict[str, Any] | None = None) -> mcp.types.CompleteResult
|
||||
|
|
@ -393,7 +421,7 @@ containing the completion and any additional metadata.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `complete` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L772" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `complete` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L821" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
complete(self, ref: mcp.types.ResourceTemplateReference | mcp.types.PromptReference, argument: dict[str, str], context_arguments: dict[str, Any] | None = None) -> mcp.types.Completion
|
||||
|
|
@ -414,7 +442,7 @@ include with the completion request. Defaults to None.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `list_tools_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L799" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_tools_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L848" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_tools_mcp(self) -> mcp.types.ListToolsResult
|
||||
|
|
@ -430,7 +458,7 @@ containing the list of tools and any additional metadata.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `list_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L814" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L863" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_tools(self) -> list[mcp.types.Tool]
|
||||
|
|
@ -445,7 +473,7 @@ Retrieve a list of tools available on the server.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `call_tool_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L828" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `call_tool_mcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L877" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
call_tool_mcp(self, name: str, arguments: dict[str, Any], progress_handler: ProgressHandler | None = None, timeout: datetime.timedelta | float | int | None = None) -> mcp.types.CallToolResult
|
||||
|
|
@ -470,7 +498,7 @@ containing the tool result and any additional metadata.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `call_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L865" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `call_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L915" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
call_tool(self, name: str, arguments: dict[str, Any] | None = None, timeout: datetime.timedelta | float | int | None = None, progress_handler: ProgressHandler | None = None, raise_on_error: bool = True) -> CallToolResult
|
||||
|
|
@ -500,10 +528,10 @@ raw result object.
|
|||
- `RuntimeError`: If called while the client is not connected.
|
||||
|
||||
|
||||
#### `generate_name` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L936" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `generate_name` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L987" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
generate_name(cls, name: str | None = None) -> str
|
||||
```
|
||||
|
||||
### `CallToolResult` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L945" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `CallToolResult` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/client.py#L996" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ close(self)
|
|||
Close the transport.
|
||||
|
||||
|
||||
### `WSTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L121" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `WSTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L120" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport implementation that connects to an MCP server via WebSockets.
|
||||
|
|
@ -110,13 +110,13 @@ Transport implementation that connects to an MCP server via WebSockets.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L139" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L138" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
|
||||
```
|
||||
|
||||
### `SSETransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L160" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `SSETransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L159" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport implementation that connects to an MCP server via Server-Sent Events.
|
||||
|
|
@ -124,13 +124,13 @@ Transport implementation that connects to an MCP server via Server-Sent Events.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L196" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L195" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
|
||||
```
|
||||
|
||||
### `StreamableHttpTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L230" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `StreamableHttpTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L229" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport implementation that connects to an MCP server via Streamable HTTP Requests.
|
||||
|
|
@ -138,13 +138,13 @@ Transport implementation that connects to an MCP server via Streamable HTTP Requ
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L266" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L265" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
|
||||
```
|
||||
|
||||
### `StdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L301" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `StdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L300" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Base transport for connecting to an MCP server via subprocess with stdio.
|
||||
|
|
@ -155,67 +155,67 @@ transports like Python, Node, Uvx, etc.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L351" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L350" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
|
||||
```
|
||||
|
||||
#### `connect` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L363" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `connect` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L362" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
connect(self, **session_kwargs: Unpack[SessionKwargs]) -> ClientSession | None
|
||||
```
|
||||
|
||||
#### `disconnect` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L398" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `disconnect` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L397" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
disconnect(self)
|
||||
```
|
||||
|
||||
#### `close` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L413" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `close` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L412" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
close(self)
|
||||
```
|
||||
|
||||
### `PythonStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L484" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `PythonStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L482" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport for running Python scripts.
|
||||
|
||||
|
||||
### `FastMCPStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L537" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `FastMCPStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L535" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport for running FastMCP servers using the FastMCP CLI.
|
||||
|
||||
|
||||
### `NodeStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L566" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `NodeStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L564" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport for running Node.js scripts.
|
||||
|
||||
|
||||
### `UvStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L619" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `UvStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L617" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport for running commands via the uv tool.
|
||||
|
||||
|
||||
### `UvxStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L698" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `UvxStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L696" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport for running commands via the uvx tool.
|
||||
|
||||
|
||||
### `NpxStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L763" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `NpxStdioTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L761" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Transport for running commands via the npx tool.
|
||||
|
||||
|
||||
### `FastMCPTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L825" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `FastMCPTransport` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L823" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
In-memory transport for FastMCP servers.
|
||||
|
|
@ -228,7 +228,7 @@ tests or scenarios where client and server run in the same runtime.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L844" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `connect_session` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/client/transports.py#L842" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession]
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ infer_transport_type_from_url(url: str | AnyUrl) -> Literal['http', 'sse']
|
|||
Infer the appropriate transport type from the given URL.
|
||||
|
||||
|
||||
### `update_config_file` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L313" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `update_config_file` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L312" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
update_config_file(file_path: Path, server_name: str, server_config: CanonicalMCPServerTypes) -> None
|
||||
|
|
@ -167,7 +167,7 @@ from_file(cls, file_path: Path) -> Self
|
|||
Load configuration from JSON file.
|
||||
|
||||
|
||||
### `CanonicalMCPConfig` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L298" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `CanonicalMCPConfig` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L297" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Canonical MCP configuration format.
|
||||
|
|
@ -178,7 +178,7 @@ The format is designed to be client-agnostic and extensible for future use cases
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `add_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L308" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `add_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/mcp_config.py#L307" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_server(self, name: str, server: CanonicalMCPServerTypes) -> None
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ The function can return:
|
|||
- A sequence of any of the above
|
||||
|
||||
|
||||
#### `render` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt.py#L324" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `render` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/prompts/prompt.py#L318" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
render(self, arguments: dict[str, Any] | None = None) -> list[PromptMessage]
|
||||
|
|
|
|||
|
|
@ -36,6 +36,26 @@ create_consent_html(client_id: str, redirect_uri: str, scopes: list[str], txn_id
|
|||
Create a styled HTML consent page for OAuth authorization requests.
|
||||
|
||||
|
||||
### `create_error_html` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L390" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
create_error_html(error_title: str, error_message: str, error_details: dict[str, str] | None = None, server_name: str | None = None, server_icon_url: str | None = None) -> str
|
||||
```
|
||||
|
||||
|
||||
Create a styled HTML error page for OAuth errors.
|
||||
|
||||
**Args:**
|
||||
- `error_title`: The error title (e.g., "OAuth Error", "Authorization Failed")
|
||||
- `error_message`: The main error message to display
|
||||
- `error_details`: Optional dictionary of error details to show (e.g., {"Error Code"\: "invalid_client"})
|
||||
- `server_name`: Optional server name to display
|
||||
- `server_icon_url`: Optional URL to server icon/logo
|
||||
|
||||
**Returns:**
|
||||
- Complete HTML page as a string
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `OAuthTransaction` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L108" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
|
@ -119,7 +139,7 @@ This is essential for cached token scenarios where the client may
|
|||
reconnect with a different port.
|
||||
|
||||
|
||||
### `TokenHandler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L383" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `TokenHandler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L485" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
TokenHandler that returns OAuth 2.1 compliant error responses.
|
||||
|
|
@ -142,7 +162,7 @@ Per MCP spec: "Invalid or expired tokens MUST receive a HTTP 401 response."
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `response` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L402" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `response` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L504" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
response(self, obj: TokenSuccessResponse | TokenErrorResponse)
|
||||
|
|
@ -151,7 +171,7 @@ response(self, obj: TokenSuccessResponse | TokenErrorResponse)
|
|||
Override response method to provide OAuth 2.1 compliant error handling.
|
||||
|
||||
|
||||
### `OAuthProxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L432" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `OAuthProxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L534" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
OAuth provider that presents a DCR-compliant interface while proxying to non-DCR IDPs.
|
||||
|
|
@ -261,7 +281,7 @@ Handles provider-specific requirements:
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `get_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L813" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L915" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_client(self, client_id: str) -> OAuthClientInformationFull | None
|
||||
|
|
@ -273,7 +293,7 @@ provided to the DCR client during registration, not the upstream client ID.
|
|||
For unregistered clients, returns None (which will raise an error in the SDK).
|
||||
|
||||
|
||||
#### `register_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L829" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `register_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L931" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
register_client(self, client_info: OAuthClientInformationFull) -> None
|
||||
|
|
@ -287,7 +307,7 @@ redirect URI will likely be localhost or unknown to the proxied IDP. The
|
|||
proxied IDP only knows about this server's fixed redirect URI.
|
||||
|
||||
|
||||
#### `authorize` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L876" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `authorize` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L978" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str
|
||||
|
|
@ -304,7 +324,7 @@ If consent is disabled (require_authorization_consent=False), skip the consent s
|
|||
and redirect directly to the upstream IdP.
|
||||
|
||||
|
||||
#### `load_authorization_code` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L951" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `load_authorization_code` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L1053" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_authorization_code(self, client: OAuthClientInformationFull, authorization_code: str) -> AuthorizationCode | None
|
||||
|
|
@ -316,7 +336,7 @@ Look up our client code and return authorization code object
|
|||
with PKCE challenge for validation.
|
||||
|
||||
|
||||
#### `exchange_authorization_code` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L994" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `exchange_authorization_code` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L1096" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
exchange_authorization_code(self, client: OAuthClientInformationFull, authorization_code: AuthorizationCode) -> OAuthToken
|
||||
|
|
@ -334,7 +354,7 @@ Implements the token factory pattern:
|
|||
PKCE validation is handled by the MCP framework before this method is called.
|
||||
|
||||
|
||||
#### `load_refresh_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L1159" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `load_refresh_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L1261" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_refresh_token(self, client: OAuthClientInformationFull, refresh_token: str) -> RefreshToken | None
|
||||
|
|
@ -343,7 +363,7 @@ load_refresh_token(self, client: OAuthClientInformationFull, refresh_token: str)
|
|||
Load refresh token from local storage.
|
||||
|
||||
|
||||
#### `exchange_refresh_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L1167" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `exchange_refresh_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L1269" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
exchange_refresh_token(self, client: OAuthClientInformationFull, refresh_token: RefreshToken, scopes: list[str]) -> OAuthToken
|
||||
|
|
@ -360,7 +380,7 @@ Implements two-tier refresh:
|
|||
6. Keep same FastMCP refresh token (unless upstream rotates)
|
||||
|
||||
|
||||
#### `load_access_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L1368" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `load_access_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L1470" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_access_token(self, token: str) -> AccessToken | None
|
||||
|
|
@ -379,7 +399,7 @@ The FastMCP JWT is a reference token - all authorization data comes
|
|||
from validating the upstream token via the TokenVerifier.
|
||||
|
||||
|
||||
#### `revoke_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L1424" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `revoke_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L1526" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
revoke_token(self, token: AccessToken | RefreshToken) -> None
|
||||
|
|
@ -391,7 +411,7 @@ Removes tokens from local storage and attempts to revoke them with
|
|||
the upstream server if a revocation endpoint is configured.
|
||||
|
||||
|
||||
#### `get_routes` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L1468" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_routes` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L1570" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_routes(self, mcp_path: str | None = None) -> list[Route]
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ that is OIDC compliant.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `get_oidc_configuration` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oidc_proxy.py#L326" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_oidc_configuration` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oidc_proxy.py#L345" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_oidc_configuration(self, config_url: AnyHttpUrl, strict: bool | None, timeout_seconds: int | None) -> OIDCConfiguration
|
||||
|
|
@ -66,7 +66,7 @@ Gets the OIDC configuration for the specified configuration URL.
|
|||
- `timeout_seconds`: HTTP request timeout in seconds
|
||||
|
||||
|
||||
#### `get_token_verifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oidc_proxy.py#L343" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_token_verifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oidc_proxy.py#L362" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_token_verifier(self) -> TokenVerifier
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ using the OAuth Proxy pattern for non-DCR OAuth flows.
|
|||
Settings for Azure OAuth provider.
|
||||
|
||||
|
||||
### `AzureProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L61" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `AzureProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L62" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Azure (Microsoft Entra) OAuth provider for FastMCP.
|
||||
|
|
@ -55,7 +55,7 @@ Setup:
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `authorize` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L270" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `authorize` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L293" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str
|
||||
|
|
|
|||
70
docs/python-sdk/fastmcp-server-auth-providers-debug.mdx
Normal file
70
docs/python-sdk/fastmcp-server-auth-providers-debug.mdx
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
title: debug
|
||||
sidebarTitle: debug
|
||||
---
|
||||
|
||||
# `fastmcp.server.auth.providers.debug`
|
||||
|
||||
|
||||
Debug token verifier for testing and special cases.
|
||||
|
||||
This module provides a flexible token verifier that delegates validation
|
||||
to a custom callable. Useful for testing, development, or scenarios where
|
||||
standard verification isn't possible (like opaque tokens without introspection).
|
||||
|
||||
Example:
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.debug import DebugTokenVerifier
|
||||
|
||||
# Accept all tokens (default - useful for testing)
|
||||
auth = DebugTokenVerifier()
|
||||
|
||||
# Custom sync validation logic
|
||||
auth = DebugTokenVerifier(validate=lambda token: token.startswith("valid-"))
|
||||
|
||||
# Custom async validation logic
|
||||
async def check_cache(token: str) -> bool:
|
||||
return await redis.exists(f"token:{token}")
|
||||
|
||||
auth = DebugTokenVerifier(validate=check_cache)
|
||||
|
||||
mcp = FastMCP("My Server", auth=auth)
|
||||
```
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `DebugTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/debug.py#L40" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Token verifier with custom validation logic.
|
||||
|
||||
This verifier delegates token validation to a user-provided callable.
|
||||
By default, it accepts all non-empty tokens (useful for testing).
|
||||
|
||||
Use cases:
|
||||
- Testing: Accept any token without real verification
|
||||
- Development: Custom validation logic for prototyping
|
||||
- Opaque tokens: When you have tokens with no introspection endpoint
|
||||
|
||||
WARNING: This bypasses standard security checks. Only use in controlled
|
||||
environments or when you understand the security implications.
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/debug.py#L77" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
verify_token(self, token: str) -> AccessToken | None
|
||||
```
|
||||
|
||||
Verify token using custom validation logic.
|
||||
|
||||
**Args:**
|
||||
- `token`: The token string to validate
|
||||
|
||||
**Returns:**
|
||||
- AccessToken if validation succeeds, None otherwise
|
||||
|
||||
|
|
@ -94,16 +94,16 @@ Use this when:
|
|||
load_access_token(self, token: str) -> AccessToken | None
|
||||
```
|
||||
|
||||
Validates the provided JWT bearer token.
|
||||
Validate a JWT bearer token and return an AccessToken when the token is valid.
|
||||
|
||||
**Args:**
|
||||
- `token`: The JWT token string to validate
|
||||
- `token`: The JWT bearer token string to validate.
|
||||
|
||||
**Returns:**
|
||||
- AccessToken object if valid, None if invalid or expired
|
||||
- AccessToken | None: An AccessToken populated from token claims if the token is valid; `None` if the token is expired, has an invalid signature or format, fails issuer/audience/scope validation, or any other validation error occurs.
|
||||
|
||||
|
||||
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L474" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L485" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
verify_token(self, token: str) -> AccessToken | None
|
||||
|
|
@ -121,7 +121,7 @@ to our existing load_access_token method.
|
|||
- AccessToken object if valid, None if invalid or expired
|
||||
|
||||
|
||||
### `StaticTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L490" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `StaticTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L501" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Simple static token verifier for testing and development.
|
||||
|
|
@ -142,7 +142,7 @@ WARNING: Never use this in production - tokens are stored in plain text!
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L524" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L535" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
verify_token(self, token: str) -> AccessToken | None
|
||||
|
|
|
|||
|
|
@ -362,7 +362,7 @@ type or dataclass or BaseModel. If it is a primitive type, an
|
|||
object schema with a single "value" field will be generated.
|
||||
|
||||
|
||||
#### `get_http_request` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L643" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_http_request` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L641" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_http_request(self) -> Request
|
||||
|
|
@ -371,7 +371,7 @@ get_http_request(self) -> Request
|
|||
Get the active starlette request.
|
||||
|
||||
|
||||
#### `set_state` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L658" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_state` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L656" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_state(self, key: str, value: Any) -> None
|
||||
|
|
@ -380,7 +380,7 @@ set_state(self, key: str, value: Any) -> None
|
|||
Set a value in the context state.
|
||||
|
||||
|
||||
#### `get_state` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L662" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_state` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/context.py#L660" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_state(self, key: str) -> Any
|
||||
|
|
|
|||
|
|
@ -7,19 +7,19 @@ sidebarTitle: dependencies
|
|||
|
||||
## Functions
|
||||
|
||||
### `get_context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L30" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `get_context` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L31" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_context() -> Context
|
||||
```
|
||||
|
||||
### `get_http_request` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L42" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `get_http_request` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L43" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_http_request() -> Request
|
||||
```
|
||||
|
||||
### `get_http_headers` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L56" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `get_http_headers` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L55" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_http_headers(include_all: bool = False) -> dict[str, str]
|
||||
|
|
@ -35,7 +35,7 @@ By default, strips problematic headers like `content-length` that cause issues i
|
|||
If `include_all` is True, all headers are returned.
|
||||
|
||||
|
||||
### `get_access_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L105" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `get_access_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L104" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_access_token() -> AccessToken | None
|
||||
|
|
|
|||
|
|
@ -46,63 +46,63 @@ unwrap(cls, values: Sequence[Self]) -> list[ReadResourceContents]
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `wrap` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L68" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `wrap` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L69" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
wrap(cls, value: ToolResult) -> Self
|
||||
```
|
||||
|
||||
#### `unwrap` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L71" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `unwrap` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L76" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
unwrap(self) -> ToolResult
|
||||
```
|
||||
|
||||
### `SharedMethodSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L77" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `SharedMethodSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L84" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Shared config for a cache method.
|
||||
|
||||
|
||||
### `ListToolsSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L84" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ListToolsSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L91" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Configuration options for Tool-related caching.
|
||||
|
||||
|
||||
### `ListResourcesSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L88" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ListResourcesSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L95" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Configuration options for Resource-related caching.
|
||||
|
||||
|
||||
### `ListPromptsSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L92" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ListPromptsSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L99" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Configuration options for Prompt-related caching.
|
||||
|
||||
|
||||
### `CallToolSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L96" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `CallToolSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L103" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Configuration options for Tool-related caching.
|
||||
|
||||
|
||||
### `ReadResourceSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L103" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ReadResourceSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L110" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Configuration options for Resource-related caching.
|
||||
|
||||
|
||||
### `GetPromptSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L107" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `GetPromptSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L114" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Configuration options for Prompt-related caching.
|
||||
|
||||
|
||||
### `ResponseCachingStatistics` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L111" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ResponseCachingStatistics` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L118" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
### `ResponseCachingMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L120" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ResponseCachingMiddleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L127" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
The response caching middleware offers a simple way to cache responses to mcp methods. The Middleware
|
||||
|
|
@ -119,7 +119,7 @@ Notes:
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `on_list_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L226" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `on_list_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L233" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_list_tools(self, context: MiddlewareContext[mcp.types.ListToolsRequest], call_next: CallNext[mcp.types.ListToolsRequest, Sequence[Tool]]) -> Sequence[Tool]
|
||||
|
|
@ -129,7 +129,7 @@ List tools from the cache, if caching is enabled, and the result is in the cache
|
|||
otherwise call the next middleware and store the result in the cache if caching is enabled.
|
||||
|
||||
|
||||
#### `on_list_resources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L266" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `on_list_resources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L273" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_list_resources(self, context: MiddlewareContext[mcp.types.ListResourcesRequest], call_next: CallNext[mcp.types.ListResourcesRequest, Sequence[Resource]]) -> Sequence[Resource]
|
||||
|
|
@ -139,7 +139,7 @@ List resources from the cache, if caching is enabled, and the result is in the c
|
|||
otherwise call the next middleware and store the result in the cache if caching is enabled.
|
||||
|
||||
|
||||
#### `on_list_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L306" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `on_list_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L313" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_list_prompts(self, context: MiddlewareContext[mcp.types.ListPromptsRequest], call_next: CallNext[mcp.types.ListPromptsRequest, Sequence[Prompt]]) -> Sequence[Prompt]
|
||||
|
|
@ -149,7 +149,7 @@ List prompts from the cache, if caching is enabled, and the result is in the cac
|
|||
otherwise call the next middleware and store the result in the cache if caching is enabled.
|
||||
|
||||
|
||||
#### `on_call_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L344" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `on_call_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L351" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_call_tool(self, context: MiddlewareContext[mcp.types.CallToolRequestParams], call_next: CallNext[mcp.types.CallToolRequestParams, ToolResult]) -> ToolResult
|
||||
|
|
@ -159,7 +159,7 @@ Call a tool from the cache, if caching is enabled, and the result is in the cach
|
|||
otherwise call the next middleware and store the result in the cache if caching is enabled.
|
||||
|
||||
|
||||
#### `on_read_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L377" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `on_read_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L384" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_read_resource(self, context: MiddlewareContext[mcp.types.ReadResourceRequestParams], call_next: CallNext[mcp.types.ReadResourceRequestParams, Sequence[ReadResourceContents]]) -> Sequence[ReadResourceContents]
|
||||
|
|
@ -169,7 +169,7 @@ Read a resource from the cache, if caching is enabled, and the result is in the
|
|||
otherwise call the next middleware and store the result in the cache if caching is enabled.
|
||||
|
||||
|
||||
#### `on_get_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L407" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `on_get_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L414" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_get_prompt(self, context: MiddlewareContext[mcp.types.GetPromptRequestParams], call_next: CallNext[mcp.types.GetPromptRequestParams, mcp.types.GetPromptResult]) -> mcp.types.GetPromptResult
|
||||
|
|
@ -179,7 +179,7 @@ Get a prompt from the cache, if caching is enabled, and the result is in the cac
|
|||
otherwise call the next middleware and store the result in the cache if caching is enabled.
|
||||
|
||||
|
||||
#### `statistics` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L447" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `statistics` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/caching.py#L454" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
statistics(self) -> ResponseCachingStatistics
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ on_notification(self, context: MiddlewareContext[mt.Notification[Any, Any]], cal
|
|||
#### `on_initialize` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L150" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_initialize(self, context: MiddlewareContext[mt.InitializeRequestParams], call_next: CallNext[mt.InitializeRequestParams, None]) -> None
|
||||
on_initialize(self, context: MiddlewareContext[mt.InitializeRequest], call_next: CallNext[mt.InitializeRequest, None]) -> None
|
||||
```
|
||||
|
||||
#### `on_call_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/middleware/middleware.py#L157" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ Resource implementation for OpenAPI endpoints.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `read` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L552" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `read` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L554" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
read(self) -> str | bytes
|
||||
|
|
@ -64,7 +64,7 @@ read(self) -> str | bytes
|
|||
Fetch the resource data by making an HTTP request.
|
||||
|
||||
|
||||
### `OpenAPIResourceTemplate` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L642" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `OpenAPIResourceTemplate` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L644" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Resource template implementation for OpenAPI endpoints.
|
||||
|
|
@ -72,7 +72,7 @@ Resource template implementation for OpenAPI endpoints.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `create_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L671" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `create_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L675" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
create_resource(self, uri: str, params: dict[str, Any], context: Context | None = None) -> Resource
|
||||
|
|
@ -81,7 +81,7 @@ create_resource(self, uri: str, params: dict[str, Any], context: Context | None
|
|||
Create a resource with the given parameters.
|
||||
|
||||
|
||||
### `FastMCPOpenAPI` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L696" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `FastMCPOpenAPI` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/openapi.py#L700" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
FastMCP server implementation that creates components from an OpenAPI schema.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: proxy
|
|||
|
||||
## Functions
|
||||
|
||||
### `default_proxy_roots_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L521" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `default_proxy_roots_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L523" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
default_proxy_roots_handler(context: RequestContext[ClientSession, LifespanContextT]) -> RootsList
|
||||
|
|
@ -113,7 +113,7 @@ read_resource(self, uri: AnyUrl | str) -> str | bytes
|
|||
Reads a resource, trying local/mounted first, then proxy if not found.
|
||||
|
||||
|
||||
### `ProxyPromptManager` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L204" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ProxyPromptManager` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L206" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A PromptManager that sources its prompts from a remote client in addition to local and mounted prompts.
|
||||
|
|
@ -121,7 +121,7 @@ A PromptManager that sources its prompts from a remote client in addition to loc
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `get_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L211" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L213" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_prompts(self) -> dict[str, Prompt]
|
||||
|
|
@ -130,7 +130,7 @@ get_prompts(self) -> dict[str, Prompt]
|
|||
Gets the unfiltered prompt inventory including local, mounted, and proxy prompts.
|
||||
|
||||
|
||||
#### `list_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L234" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `list_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L236" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
list_prompts(self) -> list[Prompt]
|
||||
|
|
@ -139,7 +139,7 @@ list_prompts(self) -> list[Prompt]
|
|||
Gets the filtered list of prompts including local, mounted, and proxy prompts.
|
||||
|
||||
|
||||
#### `render_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L239" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `render_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L241" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
render_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> GetPromptResult
|
||||
|
|
@ -148,7 +148,7 @@ render_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> GetPr
|
|||
Renders a prompt, trying local/mounted first, then proxy if not found.
|
||||
|
||||
|
||||
### `ProxyTool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L256" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ProxyTool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L258" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A Tool that represents and executes a tool on a remote server.
|
||||
|
|
@ -156,7 +156,7 @@ A Tool that represents and executes a tool on a remote server.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `from_mcp_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L266" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `from_mcp_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L268" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_mcp_tool(cls, client: Client, mcp_tool: mcp.types.Tool) -> ProxyTool
|
||||
|
|
@ -165,7 +165,7 @@ from_mcp_tool(cls, client: Client, mcp_tool: mcp.types.Tool) -> ProxyTool
|
|||
Factory method to create a ProxyTool from a raw MCP tool schema.
|
||||
|
||||
|
||||
#### `run` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L280" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `run` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L282" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run(self, arguments: dict[str, Any], context: Context | None = None) -> ToolResult
|
||||
|
|
@ -174,7 +174,7 @@ run(self, arguments: dict[str, Any], context: Context | None = None) -> ToolResu
|
|||
Executes the tool by making a call through the client.
|
||||
|
||||
|
||||
### `ProxyResource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L299" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ProxyResource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L301" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A Resource that represents and reads a resource from a remote server.
|
||||
|
|
@ -182,7 +182,7 @@ A Resource that represents and reads a resource from a remote server.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `from_mcp_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L319" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `from_mcp_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L321" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_mcp_resource(cls, client: Client, mcp_resource: mcp.types.Resource) -> ProxyResource
|
||||
|
|
@ -191,7 +191,7 @@ from_mcp_resource(cls, client: Client, mcp_resource: mcp.types.Resource) -> Prox
|
|||
Factory method to create a ProxyResource from a raw MCP resource schema.
|
||||
|
||||
|
||||
#### `read` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L337" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `read` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L339" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
read(self) -> str | bytes
|
||||
|
|
@ -200,7 +200,7 @@ read(self) -> str | bytes
|
|||
Read the resource content from the remote server.
|
||||
|
||||
|
||||
### `ProxyTemplate` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L352" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ProxyTemplate` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L354" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A ResourceTemplate that represents and creates resources from a remote server template.
|
||||
|
|
@ -208,7 +208,7 @@ A ResourceTemplate that represents and creates resources from a remote server te
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `from_mcp_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L362" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `from_mcp_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L364" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_mcp_template(cls, client: Client, mcp_template: mcp.types.ResourceTemplate) -> ProxyTemplate
|
||||
|
|
@ -217,7 +217,7 @@ from_mcp_template(cls, client: Client, mcp_template: mcp.types.ResourceTemplate)
|
|||
Factory method to create a ProxyTemplate from a raw MCP template schema.
|
||||
|
||||
|
||||
#### `create_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L378" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `create_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L380" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
create_resource(self, uri: str, params: dict[str, Any], context: Context | None = None) -> ProxyResource
|
||||
|
|
@ -226,7 +226,7 @@ create_resource(self, uri: str, params: dict[str, Any], context: Context | None
|
|||
Create a resource from the template by calling the remote server.
|
||||
|
||||
|
||||
### `ProxyPrompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L413" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ProxyPrompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L415" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A Prompt that represents and renders a prompt from a remote server.
|
||||
|
|
@ -234,7 +234,7 @@ A Prompt that represents and renders a prompt from a remote server.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `from_mcp_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L425" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `from_mcp_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L427" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_mcp_prompt(cls, client: Client, mcp_prompt: mcp.types.Prompt) -> ProxyPrompt
|
||||
|
|
@ -243,7 +243,7 @@ from_mcp_prompt(cls, client: Client, mcp_prompt: mcp.types.Prompt) -> ProxyPromp
|
|||
Factory method to create a ProxyPrompt from a raw MCP prompt schema.
|
||||
|
||||
|
||||
#### `render` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L447" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `render` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L449" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
render(self, arguments: dict[str, Any]) -> list[PromptMessage]
|
||||
|
|
@ -252,14 +252,14 @@ render(self, arguments: dict[str, Any]) -> list[PromptMessage]
|
|||
Render the prompt by making a call through the client.
|
||||
|
||||
|
||||
### `FastMCPProxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L454" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `FastMCPProxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L456" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A FastMCP server that acts as a proxy to a remote MCP-compliant server.
|
||||
It uses specialized managers that fulfill requests via a client factory.
|
||||
|
||||
|
||||
### `ProxyClient` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L531" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ProxyClient` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L533" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A proxy client that forwards advanced interactions between a remote MCP server and the proxy's connected clients.
|
||||
|
|
@ -268,7 +268,7 @@ Supports forwarding roots, sampling, elicitation, logging, and progress.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `default_sampling_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L564" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `default_sampling_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L566" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
default_sampling_handler(cls, messages: list[mcp.types.SamplingMessage], params: mcp.types.CreateMessageRequestParams, context: RequestContext[ClientSession, LifespanContextT]) -> mcp.types.CreateMessageResult
|
||||
|
|
@ -277,7 +277,7 @@ default_sampling_handler(cls, messages: list[mcp.types.SamplingMessage], params:
|
|||
A handler that forwards the sampling request from the remote server to the proxy's connected clients and relays the response back to the remote server.
|
||||
|
||||
|
||||
#### `default_elicitation_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L590" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `default_elicitation_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L592" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
default_elicitation_handler(cls, message: str, response_type: type, params: mcp.types.ElicitRequestParams, context: RequestContext[ClientSession, LifespanContextT]) -> ElicitResult
|
||||
|
|
@ -286,7 +286,7 @@ default_elicitation_handler(cls, message: str, response_type: type, params: mcp.
|
|||
A handler that forwards the elicitation request from the remote server to the proxy's connected clients and relays the response back to the remote server.
|
||||
|
||||
|
||||
#### `default_log_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L609" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `default_log_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L611" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
default_log_handler(cls, message: LogMessage) -> None
|
||||
|
|
@ -295,7 +295,7 @@ default_log_handler(cls, message: LogMessage) -> None
|
|||
A handler that forwards the log notification from the remote server to the proxy's connected clients.
|
||||
|
||||
|
||||
#### `default_progress_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L619" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `default_progress_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L621" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
default_progress_handler(cls, progress: float, total: float | None, message: str | None) -> None
|
||||
|
|
@ -304,7 +304,7 @@ default_progress_handler(cls, progress: float, total: float | None, message: str
|
|||
A handler that forwards the progress notification from the remote server to the proxy's connected clients.
|
||||
|
||||
|
||||
### `StatefulProxyClient` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L632" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `StatefulProxyClient` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L634" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A proxy client that provides a stateful client factory for the proxy server.
|
||||
|
|
@ -318,7 +318,7 @@ Note that it is essential to ensure that the proxy server itself is also statefu
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `clear` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L654" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `clear` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L655" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
clear(self)
|
||||
|
|
@ -327,7 +327,7 @@ clear(self)
|
|||
Clear all cached clients and force disconnect them.
|
||||
|
||||
|
||||
#### `new_stateful` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L662" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `new_stateful` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/proxy.py#L663" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
new_stateful(self) -> Client[ClientTransportT]
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ FastMCP - A more ergonomic interface for MCP servers.
|
|||
|
||||
## Functions
|
||||
|
||||
### `default_lifespan` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L108" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `default_lifespan` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L113" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
default_lifespan(server: FastMCP[LifespanResultT]) -> AsyncIterator[Any]
|
||||
|
|
@ -26,7 +26,7 @@ Default lifespan context manager that does nothing.
|
|||
- An empty dictionary as the lifespan result.
|
||||
|
||||
|
||||
### `add_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2705" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `add_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2709" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> str
|
||||
|
|
@ -64,7 +64,7 @@ add_resource_prefix("resource:///absolute/path", "prefix")
|
|||
- `ValueError`: If the URI doesn't match the expected protocol\://path format
|
||||
|
||||
|
||||
### `remove_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2765" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `remove_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2769" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
remove_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> str
|
||||
|
|
@ -103,7 +103,7 @@ remove_resource_prefix("resource://prefix//absolute/path", "prefix")
|
|||
- `ValueError`: If the URI doesn't match the expected protocol\://path format
|
||||
|
||||
|
||||
### `has_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2832" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `has_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2836" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
has_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> bool
|
||||
|
|
@ -143,53 +143,53 @@ False
|
|||
|
||||
## Classes
|
||||
|
||||
### `FastMCP` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L144" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `FastMCP` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L149" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `settings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L344" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `settings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L349" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
settings(self) -> Settings
|
||||
```
|
||||
|
||||
#### `name` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L355" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `name` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L360" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
name(self) -> str
|
||||
```
|
||||
|
||||
#### `instructions` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L359" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `instructions` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L364" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
instructions(self) -> str | None
|
||||
```
|
||||
|
||||
#### `instructions` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L363" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `instructions` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L368" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
instructions(self, value: str | None) -> None
|
||||
```
|
||||
|
||||
#### `version` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L367" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `version` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L372" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
version(self) -> str | None
|
||||
```
|
||||
|
||||
#### `website_url` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L371" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `website_url` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L376" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
website_url(self) -> str | None
|
||||
```
|
||||
|
||||
#### `icons` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L375" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `icons` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L380" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
icons(self) -> list[mcp.types.Icon]
|
||||
```
|
||||
|
||||
#### `run_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L402" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `run_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L407" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run_async(self, transport: Transport | None = None, show_banner: bool = True, **transport_kwargs: Any) -> None
|
||||
|
|
@ -201,7 +201,7 @@ Run the FastMCP server asynchronously.
|
|||
- `transport`: Transport protocol to use ("stdio", "sse", or "streamable-http")
|
||||
|
||||
|
||||
#### `run` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L432" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `run` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L437" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run(self, transport: Transport | None = None, show_banner: bool = True, **transport_kwargs: Any) -> None
|
||||
|
|
@ -213,13 +213,13 @@ Run the FastMCP server. Note this is a synchronous function.
|
|||
- `transport`: Transport protocol to use ("stdio", "sse", or "streamable-http")
|
||||
|
||||
|
||||
#### `add_middleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L476" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `add_middleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L481" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_middleware(self, middleware: Middleware) -> None
|
||||
```
|
||||
|
||||
#### `get_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L479" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L484" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_tools(self) -> dict[str, Tool]
|
||||
|
|
@ -228,13 +228,13 @@ get_tools(self) -> dict[str, Tool]
|
|||
Get all tools (unfiltered), including mounted servers, indexed by key.
|
||||
|
||||
|
||||
#### `get_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L499" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L504" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_tool(self, key: str) -> Tool
|
||||
```
|
||||
|
||||
#### `get_resources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L505" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_resources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L510" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_resources(self) -> dict[str, Resource]
|
||||
|
|
@ -243,13 +243,13 @@ get_resources(self) -> dict[str, Resource]
|
|||
Get all resources (unfiltered), including mounted servers, indexed by key.
|
||||
|
||||
|
||||
#### `get_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L538" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L543" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_resource(self, key: str) -> Resource
|
||||
```
|
||||
|
||||
#### `get_resource_templates` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L544" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_resource_templates` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L549" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_resource_templates(self) -> dict[str, ResourceTemplate]
|
||||
|
|
@ -258,7 +258,7 @@ get_resource_templates(self) -> dict[str, ResourceTemplate]
|
|||
Get all resource templates (unfiltered), including mounted servers, indexed by key.
|
||||
|
||||
|
||||
#### `get_resource_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L577" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_resource_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L582" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_resource_template(self, key: str) -> ResourceTemplate
|
||||
|
|
@ -267,7 +267,7 @@ get_resource_template(self, key: str) -> ResourceTemplate
|
|||
Get a registered resource template by key.
|
||||
|
||||
|
||||
#### `get_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L584" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L589" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_prompts(self) -> dict[str, Prompt]
|
||||
|
|
@ -276,13 +276,13 @@ get_prompts(self) -> dict[str, Prompt]
|
|||
Get all prompts (unfiltered), including mounted servers, indexed by key.
|
||||
|
||||
|
||||
#### `get_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L604" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L609" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_prompt(self, key: str) -> Prompt
|
||||
```
|
||||
|
||||
#### `custom_route` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L610" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `custom_route` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L615" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
custom_route(self, path: str, methods: list[str], name: str | None = None, include_in_schema: bool = True) -> Callable[[Callable[[Request], Awaitable[Response]]], Callable[[Request], Awaitable[Response]]]
|
||||
|
|
@ -303,7 +303,7 @@ Starlette's reverse URL lookup feature)
|
|||
- `include_in_schema`: Whether to include in OpenAPI schema, defaults to True
|
||||
|
||||
|
||||
#### `add_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1307" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `add_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1313" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_tool(self, tool: Tool) -> Tool
|
||||
|
|
@ -321,7 +321,7 @@ with the Context type annotation. See the @tool decorator for examples.
|
|||
- The tool instance that was added to the server.
|
||||
|
||||
|
||||
#### `remove_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1332" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `remove_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1338" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
remove_tool(self, name: str) -> None
|
||||
|
|
@ -336,7 +336,7 @@ Remove a tool from the server.
|
|||
- `NotFoundError`: If the tool is not found
|
||||
|
||||
|
||||
#### `add_tool_transformation` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1352" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `add_tool_transformation` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1358" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_tool_transformation(self, tool_name: str, transformation: ToolTransformConfig) -> None
|
||||
|
|
@ -345,7 +345,7 @@ add_tool_transformation(self, tool_name: str, transformation: ToolTransformConfi
|
|||
Add a tool transformation.
|
||||
|
||||
|
||||
#### `remove_tool_transformation` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1358" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `remove_tool_transformation` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1364" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
remove_tool_transformation(self, tool_name: str) -> None
|
||||
|
|
@ -354,19 +354,19 @@ remove_tool_transformation(self, tool_name: str) -> None
|
|||
Remove a tool transformation.
|
||||
|
||||
|
||||
#### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1363" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1369" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
tool(self, name_or_fn: AnyFunction) -> FunctionTool
|
||||
```
|
||||
|
||||
#### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1380" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1386" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
tool(self, name_or_fn: str | None = None) -> Callable[[AnyFunction], FunctionTool]
|
||||
```
|
||||
|
||||
#### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1396" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1402" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
tool(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionTool] | FunctionTool
|
||||
|
|
@ -422,7 +422,7 @@ server.tool(my_function, name="custom_name")
|
|||
```
|
||||
|
||||
|
||||
#### `add_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1530" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `add_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1536" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_resource(self, resource: Resource) -> Resource
|
||||
|
|
@ -437,7 +437,7 @@ Add a resource to the server.
|
|||
- The resource instance that was added to the server.
|
||||
|
||||
|
||||
#### `add_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1552" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `add_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1558" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_template(self, template: ResourceTemplate) -> ResourceTemplate
|
||||
|
|
@ -452,7 +452,7 @@ Add a resource template to the server.
|
|||
- The template instance that was added to the server.
|
||||
|
||||
|
||||
#### `add_resource_fn` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1574" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `add_resource_fn` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1580" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_resource_fn(self, fn: AnyFunction, uri: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None) -> None
|
||||
|
|
@ -472,7 +472,7 @@ has parameters, it will be registered as a template resource.
|
|||
- `tags`: Optional set of tags for categorizing the resource
|
||||
|
||||
|
||||
#### `resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1612" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1618" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
resource(self, uri: str) -> Callable[[AnyFunction], Resource | ResourceTemplate]
|
||||
|
|
@ -532,7 +532,7 @@ async def get_weather(city: str) -> str:
|
|||
```
|
||||
|
||||
|
||||
#### `add_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1752" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `add_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1758" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_prompt(self, prompt: Prompt) -> Prompt
|
||||
|
|
@ -547,19 +547,19 @@ Add a prompt to the server.
|
|||
- The prompt instance that was added to the server.
|
||||
|
||||
|
||||
#### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1775" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1781" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
prompt(self, name_or_fn: AnyFunction) -> FunctionPrompt
|
||||
```
|
||||
|
||||
#### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1789" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1795" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
prompt(self, name_or_fn: str | None = None) -> Callable[[AnyFunction], FunctionPrompt]
|
||||
```
|
||||
|
||||
#### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1802" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1808" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
prompt(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt
|
||||
|
|
@ -637,7 +637,7 @@ Decorator to register a prompt.
|
|||
```
|
||||
|
||||
|
||||
#### `run_stdio_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1946" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `run_stdio_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1952" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run_stdio_async(self, show_banner: bool = True, log_level: str | None = None) -> None
|
||||
|
|
@ -650,7 +650,7 @@ Run the server using stdio transport.
|
|||
- `log_level`: Log level for the server
|
||||
|
||||
|
||||
#### `run_http_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1976" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `run_http_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1982" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run_http_async(self, show_banner: bool = True, transport: Literal['http', 'streamable-http', 'sse'] = 'http', host: str | None = None, port: int | None = None, log_level: str | None = None, path: str | None = None, uvicorn_config: dict[str, Any] | None = None, middleware: list[ASGIMiddleware] | None = None, json_response: bool | None = None, stateless_http: bool | None = None) -> None
|
||||
|
|
@ -670,7 +670,7 @@ Run the server using HTTP transport.
|
|||
- `stateless_http`: Whether to use stateless HTTP (defaults to settings.stateless_http)
|
||||
|
||||
|
||||
#### `run_sse_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2055" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `run_sse_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2061" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run_sse_async(self, host: str | None = None, port: int | None = None, log_level: str | None = None, path: str | None = None, uvicorn_config: dict[str, Any] | None = None) -> None
|
||||
|
|
@ -679,7 +679,7 @@ run_sse_async(self, host: str | None = None, port: int | None = None, log_level:
|
|||
Run the server using SSE transport.
|
||||
|
||||
|
||||
#### `sse_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2083" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `sse_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2089" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
sse_app(self, path: str | None = None, message_path: str | None = None, middleware: list[ASGIMiddleware] | None = None) -> StarletteWithLifespan
|
||||
|
|
@ -693,7 +693,7 @@ Create a Starlette app for the SSE server.
|
|||
- `middleware`: A list of middleware to apply to the app
|
||||
|
||||
|
||||
#### `streamable_http_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2114" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `streamable_http_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2120" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
streamable_http_app(self, path: str | None = None, middleware: list[ASGIMiddleware] | None = None) -> StarletteWithLifespan
|
||||
|
|
@ -706,7 +706,7 @@ Create a Starlette app for the StreamableHTTP server.
|
|||
- `middleware`: A list of middleware to apply to the app
|
||||
|
||||
|
||||
#### `http_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2135" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `http_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2141" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
http_app(self, path: str | None = None, middleware: list[ASGIMiddleware] | None = None, json_response: bool | None = None, stateless_http: bool | None = None, transport: Literal['http', 'streamable-http', 'sse'] = 'http') -> StarletteWithLifespan
|
||||
|
|
@ -723,7 +723,7 @@ Create a Starlette app using the specified HTTP transport.
|
|||
- A Starlette application configured with the specified transport
|
||||
|
||||
|
||||
#### `mount` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2209" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `mount` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2215" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
mount(self, server: FastMCP[LifespanResultT], prefix: str | None = None, as_proxy: bool | None = None) -> None
|
||||
|
|
@ -777,7 +777,7 @@ automatically determined based on whether the server has a custom lifespan
|
|||
- `prompt_separator`: Deprecated. Separator character for prompt names.
|
||||
|
||||
|
||||
#### `import_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2328" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `import_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2334" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
import_server(self, server: FastMCP[LifespanResultT], prefix: str | None = None, tool_separator: str | None = None, resource_separator: str | None = None, prompt_separator: str | None = None) -> None
|
||||
|
|
@ -818,7 +818,7 @@ applied using the protocol\://prefix/path format
|
|||
- `prompt_separator`: Deprecated. Separator for prompt names.
|
||||
|
||||
|
||||
#### `from_openapi` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2466" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `from_openapi` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2472" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_openapi(cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient, route_maps: list[RouteMap] | list[RouteMapNew] | None = None, route_map_fn: OpenAPIRouteMapFn | OpenAPIRouteMapFnNew | None = None, mcp_component_fn: OpenAPIComponentFn | OpenAPIComponentFnNew | None = None, mcp_names: dict[str, str] | None = None, tags: set[str] | None = None, **settings: Any) -> FastMCPOpenAPI | FastMCPOpenAPINew
|
||||
|
|
@ -827,7 +827,7 @@ from_openapi(cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient, route
|
|||
Create a FastMCP server from an OpenAPI specification.
|
||||
|
||||
|
||||
#### `from_fastapi` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2515" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `from_fastapi` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2521" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_fastapi(cls, app: Any, name: str | None = None, route_maps: list[RouteMap] | list[RouteMapNew] | None = None, route_map_fn: OpenAPIRouteMapFn | OpenAPIRouteMapFnNew | None = None, mcp_component_fn: OpenAPIComponentFn | OpenAPIComponentFnNew | None = None, mcp_names: dict[str, str] | None = None, httpx_client_kwargs: dict[str, Any] | None = None, tags: set[str] | None = None, **settings: Any) -> FastMCPOpenAPI | FastMCPOpenAPINew
|
||||
|
|
@ -836,10 +836,10 @@ from_fastapi(cls, app: Any, name: str | None = None, route_maps: list[RouteMap]
|
|||
Create a FastMCP server from a FastAPI application.
|
||||
|
||||
|
||||
#### `as_proxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2578" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `as_proxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2584" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
as_proxy(cls, backend: Client[ClientTransportT] | ClientTransport | FastMCP[Any] | AnyUrl | Path | MCPConfig | dict[str, Any] | str, **settings: Any) -> FastMCPProxy
|
||||
as_proxy(cls, backend: Client[ClientTransportT] | ClientTransport | FastMCP[Any] | FastMCP1Server | AnyUrl | Path | MCPConfig | dict[str, Any] | str, **settings: Any) -> FastMCPProxy
|
||||
```
|
||||
|
||||
Create a FastMCP proxy server for the given backend.
|
||||
|
|
@ -850,7 +850,7 @@ instance or any value accepted as the `transport` argument of
|
|||
`fastmcp.client.Client` constructor.
|
||||
|
||||
|
||||
#### `from_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2637" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `from_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2644" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_client(cls, client: Client[ClientTransportT], **settings: Any) -> FastMCPProxy
|
||||
|
|
@ -859,10 +859,10 @@ from_client(cls, client: Client[ClientTransportT], **settings: Any) -> FastMCPPr
|
|||
Create a FastMCP proxy server from a FastMCP client.
|
||||
|
||||
|
||||
#### `generate_name` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2689" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `generate_name` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2693" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
generate_name(cls, name: str | None = None) -> str
|
||||
```
|
||||
|
||||
### `MountedServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2699" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `MountedServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2703" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
|
|
|||
|
|
@ -19,13 +19,13 @@ default_serializer(data: Any) -> str
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `to_mcp_result` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L97" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `to_mcp_result` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L99" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
to_mcp_result(self) -> list[ContentBlock] | tuple[list[ContentBlock], dict[str, Any]]
|
||||
to_mcp_result(self) -> list[ContentBlock] | tuple[list[ContentBlock], dict[str, Any]] | CallToolResult
|
||||
```
|
||||
|
||||
### `Tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L105" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `Tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L115" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Internal tool registration info.
|
||||
|
|
@ -33,19 +33,19 @@ Internal tool registration info.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `enable` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L123" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `enable` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L133" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
enable(self) -> None
|
||||
```
|
||||
|
||||
#### `disable` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L131" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `disable` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L141" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
disable(self) -> None
|
||||
```
|
||||
|
||||
#### `to_mcp_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L139" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `to_mcp_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L149" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
to_mcp_tool(self, **overrides: Any) -> MCPTool
|
||||
|
|
@ -54,16 +54,16 @@ to_mcp_tool(self, **overrides: Any) -> MCPTool
|
|||
Convert the FastMCP tool to an MCP tool.
|
||||
|
||||
|
||||
#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L167" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L177" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_function(fn: Callable[..., Any], name: str | None = None, title: str | None = None, description: str | None = None, icons: list[Icon] | None = None, tags: set[str] | None = None, annotations: ToolAnnotations | None = None, exclude_args: list[str] | None = None, output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet, serializer: ToolResultSerializerType | None = None, meta: dict[str, Any] | None = None, enabled: bool | None = None) -> FunctionTool
|
||||
from_function(fn: Callable[..., Any], name: str | None = None, title: str | None = None, description: str | None = None, icons: list[Icon] | None = None, tags: set[str] | None = None, annotations: ToolAnnotations | None = None, exclude_args: list[str] | None = None, output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet, serializer: ToolResultSerializerType | None = None, meta: dict[str, Any] | None = None, enabled: bool | None = None) -> FunctionTool
|
||||
```
|
||||
|
||||
Create a Tool from a function.
|
||||
|
||||
|
||||
#### `run` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L197" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `run` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L207" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run(self, arguments: dict[str, Any]) -> ToolResult
|
||||
|
|
@ -78,26 +78,26 @@ implemented by subclasses.
|
|||
(list of ContentBlocks, dict of structured output).
|
||||
|
||||
|
||||
#### `from_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L210" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `from_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L220" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_tool(cls, tool: Tool) -> TransformedTool
|
||||
```
|
||||
|
||||
### `FunctionTool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L244" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `FunctionTool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L254" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L248" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L258" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_function(cls, fn: Callable[..., Any], name: str | None = None, title: str | None = None, description: str | None = None, icons: list[Icon] | None = None, tags: set[str] | None = None, annotations: ToolAnnotations | None = None, exclude_args: list[str] | None = None, output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet, serializer: ToolResultSerializerType | None = None, meta: dict[str, Any] | None = None, enabled: bool | None = None) -> FunctionTool
|
||||
from_function(cls, fn: Callable[..., Any], name: str | None = None, title: str | None = None, description: str | None = None, icons: list[Icon] | None = None, tags: set[str] | None = None, annotations: ToolAnnotations | None = None, exclude_args: list[str] | None = None, output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet, serializer: ToolResultSerializerType | None = None, meta: dict[str, Any] | None = None, enabled: bool | None = None) -> FunctionTool
|
||||
```
|
||||
|
||||
Create a Tool from a function.
|
||||
|
||||
|
||||
#### `run` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L308" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `run` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L318" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
run(self, arguments: dict[str, Any]) -> ToolResult
|
||||
|
|
@ -106,11 +106,11 @@ run(self, arguments: dict[str, Any]) -> ToolResult
|
|||
Run the tool with arguments.
|
||||
|
||||
|
||||
### `ParsedFunction` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L360" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ParsedFunction` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L370" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L368" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `from_function` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool.py#L378" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_function(cls, fn: Callable[..., Any], exclude_args: list[str] | None = None, validate: bool = True, wrap_non_object_output_schema: bool = True) -> ParsedFunction
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ functions.
|
|||
#### `from_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_transform.py#L364" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_tool(cls, tool: Tool, name: str | None = None, title: str | None | NotSetT = NotSet, description: str | None | NotSetT = NotSet, tags: set[str] | None = None, transform_fn: Callable[..., Any] | None = None, transform_args: dict[str, ArgTransform] | None = None, annotations: ToolAnnotations | None | NotSetT = NotSet, output_schema: dict[str, Any] | None | NotSetT | Literal[False] = NotSet, serializer: Callable[[Any], str] | None | NotSetT = NotSet, meta: dict[str, Any] | None | NotSetT = NotSet, enabled: bool | None = None) -> TransformedTool
|
||||
from_tool(cls, tool: Tool, name: str | None = None, title: str | NotSetT | None = NotSet, description: str | NotSetT | None = NotSet, tags: set[str] | None = None, transform_fn: Callable[..., Any] | None = None, transform_args: dict[str, ArgTransform] | None = None, annotations: ToolAnnotations | NotSetT | None = NotSet, output_schema: dict[str, Any] | Literal[False] | NotSetT | None = NotSet, serializer: Callable[[Any], str] | NotSetT | None = NotSet, meta: dict[str, Any] | NotSetT | None = NotSet, enabled: bool | None = None) -> TransformedTool
|
||||
```
|
||||
|
||||
Create a transformed tool from a parent tool.
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ Build the full command with environment setup.
|
|||
- Full command ready for subprocess execution
|
||||
|
||||
|
||||
#### `prepare` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_server_config/v1/environments/base.py#L24" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `prepare` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_server_config/v1/environments/base.py#L23" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
prepare(self, output_dir: Path | None = None) -> None
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ this method performs that preparation. For sources that don't
|
|||
need preparation (e.g., local files), this is a no-op.
|
||||
|
||||
|
||||
#### `load_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_server_config/v1/sources/base.py#L23" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `load_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/mcp_server_config/v1/sources/base.py#L22" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_server(self) -> Any
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ sleeps, and cleanup issues.
|
|||
- `host`: Host to bind to (default\: "127.0.0.1")
|
||||
|
||||
|
||||
### `caplog_for_fastmcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/tests.py#L226" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `caplog_for_fastmcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/tests.py#L224" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
caplog_for_fastmcp(caplog)
|
||||
|
|
@ -76,7 +76,7 @@ Context manager to capture logs from FastMCP loggers even when propagation is di
|
|||
|
||||
## Classes
|
||||
|
||||
### `HeadlessOAuth` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/tests.py#L237" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `HeadlessOAuth` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/tests.py#L235" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
OAuth provider that bypasses browser interaction for testing.
|
||||
|
|
@ -87,7 +87,7 @@ instead of opening a browser and running a callback server. Useful for automated
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `redirect_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/tests.py#L250" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `redirect_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/tests.py#L248" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
redirect_handler(self, authorization_url: str) -> None
|
||||
|
|
@ -96,7 +96,7 @@ redirect_handler(self, authorization_url: str) -> None
|
|||
Make HTTP request to authorization URL and store response for callback handler.
|
||||
|
||||
|
||||
#### `callback_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/tests.py#L256" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `callback_handler` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/tests.py#L254" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
callback_handler(self) -> tuple[str, str | None]
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ Find the name of the kwarg that is of type kwarg_type.
|
|||
Includes union types that contain the kwarg_type, as well as Annotated types.
|
||||
|
||||
|
||||
### `replace_type` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L381" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `replace_type` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L394" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
replace_type(type_, type_map: dict[type, type])
|
||||
|
|
@ -107,7 +107,7 @@ Helper class for returning images from tools.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `to_image_content` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L215" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `to_image_content` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L229" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
to_image_content(self, mime_type: str | None = None, annotations: Annotations | None = None) -> mcp.types.ImageContent
|
||||
|
|
@ -116,7 +116,16 @@ to_image_content(self, mime_type: str | None = None, annotations: Annotations |
|
|||
Convert to MCP ImageContent.
|
||||
|
||||
|
||||
### `Audio` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L237" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `to_data_uri` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L244" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
to_data_uri(self, mime_type: str | None = None) -> str
|
||||
```
|
||||
|
||||
Get image as a data URI.
|
||||
|
||||
|
||||
### `Audio` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L250" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Helper class for returning audio from tools.
|
||||
|
|
@ -124,13 +133,13 @@ Helper class for returning audio from tools.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `to_audio_content` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L274" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `to_audio_content` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L287" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
to_audio_content(self, mime_type: str | None = None, annotations: Annotations | None = None) -> mcp.types.AudioContent
|
||||
```
|
||||
|
||||
### `File` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L295" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `File` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L308" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Helper class for returning file data from tools.
|
||||
|
|
@ -138,10 +147,10 @@ Helper class for returning file data from tools.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `to_resource_content` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L334" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `to_resource_content` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L347" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
to_resource_content(self, mime_type: str | None = None, annotations: Annotations | None = None) -> mcp.types.EmbeddedResource
|
||||
```
|
||||
|
||||
### `ContextSamplingFallbackProtocol` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L418" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ContextSamplingFallbackProtocol` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/types.py#L431" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
|
|
|||
|
|
@ -313,6 +313,35 @@ async def request_info(ctx: Context) -> dict:
|
|||
- **`ctx.client_id -> str | None`**: Get the ID of the client making the request, if provided during initialization
|
||||
- **`ctx.session_id -> str | None`**: Get the MCP session ID for session-based data sharing (HTTP transports only)
|
||||
|
||||
#### Client Metadata
|
||||
|
||||
<VersionBadge version="2.13.1" />
|
||||
|
||||
Clients can send contextual information with their requests using the `meta` parameter. This metadata is accessible through `ctx.request_context.meta` and is available for all MCP operations (tools, resources, prompts).
|
||||
|
||||
The `meta` field is `None` when clients don't provide metadata. When provided, metadata is accessible via attribute access (e.g., `meta.user_id`) rather than dictionary access. The structure of metadata is determined by the client making the request.
|
||||
|
||||
```python
|
||||
@mcp.tool
|
||||
def send_email(to: str, subject: str, body: str, ctx: Context) -> str:
|
||||
"""Send an email, logging metadata about the request."""
|
||||
|
||||
# Access client-provided metadata
|
||||
meta = ctx.request_context.meta
|
||||
|
||||
if meta:
|
||||
# Meta is accessed as an object with attribute access
|
||||
user_id = meta.user_id if hasattr(meta, 'user_id') else None
|
||||
trace_id = meta.trace_id if hasattr(meta, 'trace_id') else None
|
||||
|
||||
# Use metadata for logging, observability, etc.
|
||||
if trace_id:
|
||||
log_with_trace(f"Sending email for user {user_id}", trace_id)
|
||||
|
||||
# Send the email...
|
||||
return f"Email sent to {to}"
|
||||
```
|
||||
|
||||
<Warning>
|
||||
The MCP request is part of the low-level MCP SDK and intended for advanced use cases. Most users will not need to use it directly.
|
||||
</Warning>
|
||||
|
|
|
|||
|
|
@ -453,62 +453,89 @@ The 6/18/2025 MCP spec update [introduced](https://modelcontextprotocol.io/speci
|
|||
This automatic behavior enables clients to receive machine-readable data alongside human-readable content without requiring explicit output schemas for object-like returns.
|
||||
</Note>
|
||||
|
||||
#### Object-like Results (Automatic Structured Content)
|
||||
#### Dictionaries and Objects
|
||||
|
||||
When your tool returns a dictionary, dataclass, or Pydantic model, FastMCP automatically creates structured content from it. The structured content contains the actual object data, making it easy for clients to deserialize back to native objects.
|
||||
|
||||
<CodeGroup>
|
||||
```python Dict Return (No Schema Needed)
|
||||
```python Tool Definition
|
||||
@mcp.tool
|
||||
def get_user_data(user_id: str) -> dict:
|
||||
"""Get user data without type annotation."""
|
||||
"""Get user data."""
|
||||
return {"name": "Alice", "age": 30, "active": True}
|
||||
```
|
||||
|
||||
```json Traditional Content
|
||||
"{\n \"name\": \"Alice\",\n \"age\": 30,\n \"active\": true\n}"
|
||||
```
|
||||
|
||||
```json Structured Content (Automatic)
|
||||
```json MCP Result
|
||||
{
|
||||
"name": "Alice",
|
||||
"age": 30,
|
||||
"active": true
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "{\n \"name\": \"Alice\",\n \"age\": 30,\n \"active\": true\n}"
|
||||
}
|
||||
],
|
||||
"structuredContent": {
|
||||
"name": "Alice",
|
||||
"age": 30,
|
||||
"active": true
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
#### Non-object Results (Schema Required)
|
||||
#### Primitives and Collections
|
||||
|
||||
When your tool returns a primitive type (int, str, bool) or a collection (list, set), FastMCP needs a return type annotation to generate structured content. The annotation tells FastMCP how to validate and serialize the result.
|
||||
|
||||
Without a type annotation, the tool only produces `content`:
|
||||
|
||||
<CodeGroup>
|
||||
```python Integer Return (No Schema)
|
||||
@mcp.tool
|
||||
```python Tool Definition
|
||||
@mcp.tool
|
||||
def calculate_sum(a: int, b: int):
|
||||
"""Calculate sum without return annotation."""
|
||||
return a + b # Returns 8
|
||||
```
|
||||
|
||||
```json Traditional Content Only
|
||||
"8"
|
||||
```
|
||||
|
||||
```python Integer Return (With Schema)
|
||||
@mcp.tool
|
||||
def calculate_sum(a: int, b: int) -> int:
|
||||
"""Calculate sum with return annotation."""
|
||||
return a + b # Returns 8
|
||||
```
|
||||
|
||||
```json Traditional Content
|
||||
"8"
|
||||
```
|
||||
|
||||
```json Structured Content (From Schema)
|
||||
```json MCP Result
|
||||
{
|
||||
"result": 8
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "8"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
#### Complex Type Example
|
||||
When you add a return annotation, such as `-> int`, FastMCP generates `structuredContent` by wrapping the primitive value in a `{"result": ...}` object, since JSON schemas require object-type roots for structured output:
|
||||
|
||||
<CodeGroup>
|
||||
```python Tool Definition
|
||||
@mcp.tool
|
||||
def calculate_sum(a: int, b: int) -> int:
|
||||
"""Calculate sum with return annotation."""
|
||||
return a + b # Returns 8
|
||||
```
|
||||
|
||||
```json MCP Result
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "8"
|
||||
}
|
||||
],
|
||||
"structuredContent": {
|
||||
"result": 8
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
#### Typed Models
|
||||
|
||||
Return type annotations work with any type that can be converted to a JSON schema. Dataclasses and Pydantic models are particularly useful because FastMCP extracts their field definitions to create detailed schemas.
|
||||
|
||||
<CodeGroup>
|
||||
```python Tool Definition
|
||||
|
|
@ -526,14 +553,18 @@ class Person:
|
|||
@mcp.tool
|
||||
def get_user_profile(user_id: str) -> Person:
|
||||
"""Get a user's profile information."""
|
||||
return Person(name="Alice", age=30, email="alice@example.com")
|
||||
return Person(
|
||||
name="Alice",
|
||||
age=30,
|
||||
email="alice@example.com",
|
||||
)
|
||||
```
|
||||
|
||||
```json Generated Output Schema
|
||||
{
|
||||
"properties": {
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
"age": {"title": "Age", "type": "integer"},
|
||||
"age": {"title": "Age", "type": "integer"},
|
||||
"email": {"title": "Email", "type": "string"}
|
||||
},
|
||||
"required": ["name", "age", "email"],
|
||||
|
|
@ -542,15 +573,25 @@ def get_user_profile(user_id: str) -> Person:
|
|||
}
|
||||
```
|
||||
|
||||
```json Structured Output
|
||||
```json MCP Result
|
||||
{
|
||||
"name": "Alice",
|
||||
"age": 30,
|
||||
"email": "alice@example.com"
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "{\"name\": \"Alice\", \"age\": 30, \"email\": \"alice@example.com\"}"
|
||||
}
|
||||
],
|
||||
"structuredContent": {
|
||||
"name": "Alice",
|
||||
"age": 30,
|
||||
"email": "alice@example.com"
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
The `Person` dataclass becomes an output schema (second tab) that describes the expected format. When executed, clients receive the result (third tab) with both `content` and `structuredContent` fields.
|
||||
|
||||
### Output Schemas
|
||||
|
||||
<VersionBadge version="2.10.0" />
|
||||
|
|
@ -614,31 +655,70 @@ Schema generation works for most common types including basic types, collections
|
|||
- However, you can provide structured output without an output schema (using `ToolResult`)
|
||||
</Warning>
|
||||
|
||||
### Full Control with ToolResult
|
||||
### ToolResult and Metadata
|
||||
|
||||
For complete control over both traditional content and structured output, return a `ToolResult` object:
|
||||
For complete control over tool responses, return a `ToolResult` object. This gives you explicit control over all aspects of the tool's output: traditional content, structured data, and metadata.
|
||||
|
||||
```python
|
||||
from fastmcp.tools.tool import ToolResult
|
||||
from mcp.types import TextContent
|
||||
|
||||
@mcp.tool
|
||||
def advanced_tool() -> ToolResult:
|
||||
"""Tool with full control over output."""
|
||||
return ToolResult(
|
||||
content=[TextContent(type="text", text="Human-readable summary")],
|
||||
structured_content={"data": "value", "count": 42}
|
||||
structured_content={"data": "value", "count": 42},
|
||||
meta={"execution_time_ms": 145}
|
||||
)
|
||||
```
|
||||
|
||||
When returning `ToolResult`:
|
||||
- You control exactly what content and structured data is sent
|
||||
- Output schemas are optional - structured content can be provided without a schema
|
||||
- Clients receive both traditional content blocks and structured data
|
||||
`ToolResult` accepts three fields:
|
||||
|
||||
**`content`** - The traditional MCP content blocks that clients display to users. Can be a string (automatically converted to `TextContent`), a list of MCP content blocks, or any serializable value (converted to JSON string). At least one of `content` or `structured_content` must be provided.
|
||||
|
||||
```python
|
||||
# Simple string
|
||||
ToolResult(content="Hello, world!")
|
||||
|
||||
# List of content blocks
|
||||
ToolResult(content=[
|
||||
TextContent(type="text", text="Result: 42"),
|
||||
ImageContent(type="image", data="base64...", mimeType="image/png")
|
||||
])
|
||||
```
|
||||
|
||||
**`structured_content`** - A dictionary containing structured data that matches your tool's output schema. This enables clients to programmatically process the results. If you provide `structured_content`, it must be a dictionary or `None`. If only `structured_content` is provided, it will also be used as `content` (converted to JSON string).
|
||||
|
||||
```python
|
||||
ToolResult(
|
||||
content="Found 3 users",
|
||||
structured_content={"users": [{"name": "Alice"}, {"name": "Bob"}]}
|
||||
)
|
||||
```
|
||||
|
||||
**`meta`**
|
||||
<VersionBadge version="2.13.1" />
|
||||
Runtime metadata about the tool execution. Use this for performance metrics, debugging information, or any client-specific data that doesn't belong in the content or structured output.
|
||||
|
||||
```python
|
||||
ToolResult(
|
||||
content="Analysis complete",
|
||||
structured_content={"result": "positive"},
|
||||
meta={
|
||||
"execution_time_ms": 145,
|
||||
"model_version": "2.1",
|
||||
"confidence": 0.95
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
<Note>
|
||||
If your return type annotation cannot be converted to a JSON schema (e.g., complex custom classes without Pydantic support), the output schema will be omitted but the tool will still function normally with traditional content.
|
||||
The `meta` field in `ToolResult` is for runtime metadata about tool execution (e.g., execution time, performance metrics). This is separate from the `meta` parameter in `@mcp.tool(meta={...})`, which provides static metadata about the tool definition itself.
|
||||
</Note>
|
||||
|
||||
When returning `ToolResult`, you have full control - FastMCP won't automatically wrap or transform your data. `ToolResult` can be returned with or without an output schema.
|
||||
|
||||
## Error Handling
|
||||
|
||||
<VersionBadge version="2.4.1" />
|
||||
|
|
|
|||
41
examples/tool_result_echo.py
Normal file
41
examples/tool_result_echo.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
"""
|
||||
FastMCP Echo Server with Metadata
|
||||
|
||||
Demonstrates how to return metadata alongside content and structured data.
|
||||
The meta field can include execution details, versioning, or other information
|
||||
that clients may find useful.
|
||||
"""
|
||||
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.tools.tool import ToolResult
|
||||
|
||||
mcp = FastMCP("Echo Server")
|
||||
|
||||
|
||||
@dataclass
|
||||
class EchoData:
|
||||
data: str
|
||||
length: int
|
||||
|
||||
|
||||
@mcp.tool
|
||||
def echo(text: str) -> ToolResult:
|
||||
"""Echo text back with metadata about the operation."""
|
||||
start = time.perf_counter()
|
||||
|
||||
result = EchoData(data=text, length=len(text))
|
||||
|
||||
execution_time = (time.perf_counter() - start) * 1000
|
||||
|
||||
return ToolResult(
|
||||
content=f"Echoed: {text}",
|
||||
structured_content=result,
|
||||
meta={
|
||||
"execution_time_ms": round(execution_time, 2),
|
||||
"character_count": len(text),
|
||||
"word_count": len(text.split()),
|
||||
},
|
||||
)
|
||||
|
|
@ -7,12 +7,12 @@ dependencies = [
|
|||
"python-dotenv>=1.1.0",
|
||||
"exceptiongroup>=1.2.2",
|
||||
"httpx>=0.28.1",
|
||||
"mcp>=1.17.0,<2.0.0",
|
||||
"mcp>=1.19.0,<2.0.0",
|
||||
"openapi-pydantic>=0.5.1",
|
||||
"platformdirs>=4.0.0",
|
||||
"rich>=13.9.4",
|
||||
"cyclopts>=3.0.0",
|
||||
"authlib>=1.5.2",
|
||||
"cyclopts>=4.0.0",
|
||||
"authlib>=1.6.5",
|
||||
"pydantic[email]>=2.11.7",
|
||||
"pyperclip>=1.9.0",
|
||||
"py-key-value-aio[disk,keyring,memory]>=0.2.8,<0.3.0",
|
||||
|
|
@ -56,23 +56,23 @@ dev = [
|
|||
"fastapi>=0.115.12",
|
||||
"inline-snapshot[dirty-equals]>=0.27.2",
|
||||
"ipython>=8.12.3",
|
||||
"pdbpp>=0.10.3",
|
||||
"pre-commit",
|
||||
"psutil",
|
||||
"pdbpp>=0.11.7",
|
||||
"psutil>=7.0.0",
|
||||
"pyinstrument>=5.0.2",
|
||||
"pyperclip>=1.9.0",
|
||||
"pytest>=8.3.3",
|
||||
"pytest-asyncio>=1.2.0",
|
||||
"pytest-cov>=6.1.1",
|
||||
"pytest-env>=1.1.5",
|
||||
"pytest-flakefinder",
|
||||
"pytest-flakefinder>=1.1.0",
|
||||
"pytest-httpx>=0.35.0",
|
||||
"pytest-report>=0.2.1",
|
||||
"pytest-retry>=1.7.0",
|
||||
"pytest-timeout>=2.4.0",
|
||||
"pytest-xdist>=3.6.1",
|
||||
"ruff",
|
||||
"ty>=0.0.1a19",
|
||||
"ruff>=0.12.8",
|
||||
"ty==0.0.1a25",
|
||||
"prek>=0.2.12",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
|
@ -136,12 +136,17 @@ python-version = "3.10"
|
|||
|
||||
[tool.ty.rules]
|
||||
# Rules with too many errors to fix right now (40+ each)
|
||||
no-matching-overload = "ignore" # 126 errors
|
||||
no-matching-overload = "ignore" # 126 errors
|
||||
unknown-argument = "ignore" # 61 errors
|
||||
|
||||
# Rules with moderate errors that need more investigation
|
||||
call-non-callable = "ignore" # 7 errors
|
||||
|
||||
# NOTE: ty currently doesn't support type narrowing with isinstance() on unions
|
||||
# See: https://github.com/astral-sh/ty/issues/122 and https://github.com/astral-sh/ty/issues/1113
|
||||
# Some code uses `# ty: ignore[invalid-argument-type]` for this limitation.
|
||||
# TODO: Remove these ignores once ty supports union narrowing
|
||||
|
||||
[tool.ruff.lint]
|
||||
fixable = ["ALL"]
|
||||
ignore = [
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
"""Cursor integration for FastMCP install using Cyclopts."""
|
||||
|
||||
import base64
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Annotated
|
||||
from urllib.parse import quote, urlparse
|
||||
|
||||
import cyclopts
|
||||
from rich import print
|
||||
|
|
@ -36,8 +38,9 @@ def generate_cursor_deeplink(
|
|||
config_json = server_config.model_dump_json(exclude_none=True)
|
||||
config_b64 = base64.urlsafe_b64encode(config_json.encode()).decode()
|
||||
|
||||
# Generate the deeplink URL
|
||||
deeplink = f"cursor://anysphere.cursor-deeplink/mcp/install?name={server_name}&config={config_b64}"
|
||||
# Generate the deeplink URL with properly encoded server name
|
||||
encoded_name = quote(server_name, safe="")
|
||||
deeplink = f"cursor://anysphere.cursor-deeplink/mcp/install?name={encoded_name}&config={config_b64}"
|
||||
|
||||
return deeplink
|
||||
|
||||
|
|
@ -51,17 +54,20 @@ def open_deeplink(deeplink: str) -> bool:
|
|||
Returns:
|
||||
True if the command succeeded, False otherwise
|
||||
"""
|
||||
parsed = urlparse(deeplink)
|
||||
if parsed.scheme != "cursor":
|
||||
logger.warning(f"Invalid deeplink scheme: {parsed.scheme}")
|
||||
return False
|
||||
|
||||
try:
|
||||
if sys.platform == "darwin": # macOS
|
||||
subprocess.run(["open", deeplink], check=True, capture_output=True)
|
||||
elif sys.platform == "win32": # Windows
|
||||
subprocess.run(
|
||||
["cmd", "/c", "start", deeplink], check=True, capture_output=True
|
||||
)
|
||||
os.startfile(deeplink)
|
||||
else: # Linux and others
|
||||
subprocess.run(["xdg-open", deeplink], check=True, capture_output=True)
|
||||
return True
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
except (subprocess.CalledProcessError, FileNotFoundError, OSError):
|
||||
return False
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from key_value.aio.adapters.pydantic import PydanticAdapter
|
|||
from key_value.aio.protocols import AsyncKeyValue
|
||||
from key_value.aio.stores.memory import MemoryStore
|
||||
from mcp.client.auth import OAuthClientProvider, TokenStorage
|
||||
from mcp.shared._httpx_utils import McpHttpClientFactory
|
||||
from mcp.shared.auth import (
|
||||
OAuthClientInformationFull,
|
||||
OAuthClientMetadata,
|
||||
|
|
@ -147,6 +148,7 @@ class OAuth(OAuthClientProvider):
|
|||
token_storage: AsyncKeyValue | None = None,
|
||||
additional_client_metadata: dict[str, Any] | None = None,
|
||||
callback_port: int | None = None,
|
||||
httpx_client_factory: McpHttpClientFactory | None = None,
|
||||
):
|
||||
"""
|
||||
Initialize OAuth client provider for an MCP server.
|
||||
|
|
@ -164,6 +166,7 @@ class OAuth(OAuthClientProvider):
|
|||
server_base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
|
||||
|
||||
# Setup OAuth client
|
||||
self.httpx_client_factory = httpx_client_factory or httpx.AsyncClient
|
||||
self.redirect_port = callback_port or find_available_port()
|
||||
redirect_uri = f"http://localhost:{self.redirect_port}/callback"
|
||||
|
||||
|
|
@ -226,7 +229,7 @@ class OAuth(OAuthClientProvider):
|
|||
async def redirect_handler(self, authorization_url: str) -> None:
|
||||
"""Open browser for authorization, with pre-flight check for invalid client."""
|
||||
# Pre-flight check to detect invalid client_id before opening browser
|
||||
async with httpx.AsyncClient() as client:
|
||||
async with self.httpx_client_factory() as client:
|
||||
response = await client.get(authorization_url, follow_redirects=False)
|
||||
|
||||
# Check for client not found error (400 typically means bad client_id)
|
||||
|
|
@ -297,7 +300,8 @@ class OAuth(OAuthClientProvider):
|
|||
response = None
|
||||
while True:
|
||||
try:
|
||||
yielded_request = await gen.asend(response)
|
||||
# First iteration sends None, subsequent iterations send response
|
||||
yielded_request = await gen.asend(response) # ty: ignore[invalid-argument-type]
|
||||
response = yield yielded_request
|
||||
except StopAsyncIteration:
|
||||
break
|
||||
|
|
@ -306,16 +310,16 @@ class OAuth(OAuthClientProvider):
|
|||
logger.debug(
|
||||
"OAuth client not found on server, clearing cache and retrying..."
|
||||
)
|
||||
|
||||
# Clear cached state and retry once
|
||||
self._initialized = False
|
||||
await self.token_storage_adapter.clear()
|
||||
|
||||
# Retry with fresh registration
|
||||
gen = super().async_auth_flow(request)
|
||||
response = None
|
||||
while True:
|
||||
try:
|
||||
yielded_request = await gen.asend(response)
|
||||
yielded_request = await gen.asend(response) # ty: ignore[invalid-argument-type]
|
||||
response = yield yielded_request
|
||||
except StopAsyncIteration:
|
||||
break
|
||||
|
|
|
|||
|
|
@ -77,6 +77,16 @@ logger = get_logger(__name__)
|
|||
T = TypeVar("T", bound="ClientTransport")
|
||||
|
||||
|
||||
def _timeout_to_seconds(
|
||||
timeout: datetime.timedelta | float | int | None,
|
||||
) -> float | None:
|
||||
if timeout is None:
|
||||
return None
|
||||
if isinstance(timeout, datetime.timedelta):
|
||||
return timeout.total_seconds()
|
||||
return float(timeout)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ClientSessionState:
|
||||
"""Holds all session-related state for a Client instance.
|
||||
|
|
@ -222,6 +232,7 @@ class Client(Generic[ClientTransportT]):
|
|||
message_handler: MessageHandlerT | MessageHandler | None = None,
|
||||
progress_handler: ProgressHandler | None = None,
|
||||
timeout: datetime.timedelta | float | int | None = None,
|
||||
auto_initialize: bool = True,
|
||||
init_timeout: datetime.timedelta | float | int | None = None,
|
||||
client_info: mcp.types.Implementation | None = None,
|
||||
auth: httpx.Auth | Literal["oauth"] | str | None = None,
|
||||
|
|
@ -240,26 +251,23 @@ class Client(Generic[ClientTransportT]):
|
|||
|
||||
self._progress_handler = progress_handler
|
||||
|
||||
# Convert timeout to timedelta if needed
|
||||
if isinstance(timeout, int | float):
|
||||
timeout = datetime.timedelta(seconds=float(timeout))
|
||||
|
||||
# handle init handshake timeout
|
||||
if init_timeout is None:
|
||||
init_timeout = fastmcp.settings.client_init_timeout
|
||||
if isinstance(init_timeout, datetime.timedelta):
|
||||
init_timeout = init_timeout.total_seconds()
|
||||
elif not init_timeout:
|
||||
init_timeout = None
|
||||
else:
|
||||
init_timeout = float(init_timeout)
|
||||
self._init_timeout = init_timeout
|
||||
self._init_timeout = _timeout_to_seconds(init_timeout)
|
||||
|
||||
self.auto_initialize = auto_initialize
|
||||
|
||||
self._session_kwargs: SessionKwargs = {
|
||||
"sampling_callback": None,
|
||||
"list_roots_callback": None,
|
||||
"logging_callback": create_log_callback(log_handler),
|
||||
"message_handler": message_handler,
|
||||
"read_timeout_seconds": timeout,
|
||||
"read_timeout_seconds": timeout, # ty: ignore[invalid-argument-type]
|
||||
"client_info": client_info,
|
||||
}
|
||||
|
||||
|
|
@ -290,12 +298,8 @@ class Client(Generic[ClientTransportT]):
|
|||
return self._session_state.session
|
||||
|
||||
@property
|
||||
def initialize_result(self) -> mcp.types.InitializeResult:
|
||||
def initialize_result(self) -> mcp.types.InitializeResult | None:
|
||||
"""Get the result of the initialization request."""
|
||||
if self._session_state.initialize_result is None:
|
||||
raise RuntimeError(
|
||||
"Client is not connected. Use the 'async with client:' context manager first."
|
||||
)
|
||||
return self._session_state.initialize_result
|
||||
|
||||
def set_roots(self, roots: RootsList | RootsHandler) -> None:
|
||||
|
|
@ -357,15 +361,11 @@ class Client(Generic[ClientTransportT]):
|
|||
self._session_state.session = session
|
||||
# Initialize the session
|
||||
try:
|
||||
with anyio.fail_after(self._init_timeout):
|
||||
self._session_state.initialize_result = (
|
||||
await self._session_state.session.initialize()
|
||||
)
|
||||
if self.auto_initialize:
|
||||
await self.initialize()
|
||||
yield
|
||||
except anyio.ClosedResourceError as e:
|
||||
raise RuntimeError("Server session was closed unexpectedly") from e
|
||||
except TimeoutError as e:
|
||||
raise RuntimeError("Failed to initialize server session") from e
|
||||
finally:
|
||||
self._session_state.session = None
|
||||
self._session_state.initialize_result = None
|
||||
|
|
@ -493,6 +493,55 @@ class Client(Generic[ClientTransportT]):
|
|||
|
||||
# --- MCP Client Methods ---
|
||||
|
||||
async def initialize(
|
||||
self,
|
||||
timeout: datetime.timedelta | float | int | None = None,
|
||||
) -> mcp.types.InitializeResult:
|
||||
"""Send an initialize request to the server.
|
||||
|
||||
This method performs the MCP initialization handshake with the server,
|
||||
exchanging capabilities and server information. It is idempotent - calling
|
||||
it multiple times returns the cached result from the first call.
|
||||
|
||||
The initialization happens automatically when entering the client context
|
||||
manager unless `auto_initialize=False` was set during client construction.
|
||||
Manual calls to this method are only needed when auto-initialization is disabled.
|
||||
|
||||
Args:
|
||||
timeout: Optional timeout for the initialization request (seconds or timedelta).
|
||||
If None, uses the client's init_timeout setting.
|
||||
|
||||
Returns:
|
||||
InitializeResult: The server's initialization response containing server info,
|
||||
capabilities, protocol version, and optional instructions.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If the client is not connected or initialization times out.
|
||||
|
||||
Example:
|
||||
```python
|
||||
# With auto-initialization disabled
|
||||
client = Client(server, auto_initialize=False)
|
||||
async with client:
|
||||
result = await client.initialize()
|
||||
print(f"Server: {result.serverInfo.name}")
|
||||
print(f"Instructions: {result.instructions}")
|
||||
```
|
||||
"""
|
||||
|
||||
if self.initialize_result is not None:
|
||||
return self.initialize_result
|
||||
|
||||
if timeout is None:
|
||||
timeout = self._init_timeout
|
||||
try:
|
||||
with anyio.fail_after(_timeout_to_seconds(timeout)):
|
||||
initialize_result = await self.session.initialize()
|
||||
self._session_state.initialize_result = initialize_result
|
||||
return initialize_result
|
||||
except TimeoutError as e:
|
||||
raise RuntimeError("Failed to initialize server session") from e
|
||||
|
||||
async def ping(self) -> bool:
|
||||
"""Send a ping request."""
|
||||
result = await self.session.send_ping()
|
||||
|
|
@ -831,6 +880,7 @@ class Client(Generic[ClientTransportT]):
|
|||
arguments: dict[str, Any],
|
||||
progress_handler: ProgressHandler | None = None,
|
||||
timeout: datetime.timedelta | float | int | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
) -> mcp.types.CallToolResult:
|
||||
"""Send a tools/call request and return the complete MCP protocol result.
|
||||
|
||||
|
|
@ -842,6 +892,10 @@ class Client(Generic[ClientTransportT]):
|
|||
arguments (dict[str, Any]): Arguments to pass to the tool.
|
||||
timeout (datetime.timedelta | float | int | None, optional): The timeout for the tool call. Defaults to None.
|
||||
progress_handler (ProgressHandler | None, optional): The progress handler to use for the tool call. Defaults to None.
|
||||
meta (dict[str, Any] | None, optional): Additional metadata to include with the request.
|
||||
This is useful for passing contextual information (like user IDs, trace IDs, or preferences)
|
||||
that shouldn't be tool arguments but may influence server-side processing. The server
|
||||
can access this via `context.request_context.meta`. Defaults to None.
|
||||
|
||||
Returns:
|
||||
mcp.types.CallToolResult: The complete response object from the protocol,
|
||||
|
|
@ -852,13 +906,16 @@ class Client(Generic[ClientTransportT]):
|
|||
"""
|
||||
logger.debug(f"[{self.name}] called call_tool: {name}")
|
||||
|
||||
# Convert timeout to timedelta if needed
|
||||
if isinstance(timeout, int | float):
|
||||
timeout = datetime.timedelta(seconds=float(timeout))
|
||||
|
||||
result = await self.session.call_tool(
|
||||
name=name,
|
||||
arguments=arguments,
|
||||
read_timeout_seconds=timeout,
|
||||
read_timeout_seconds=timeout, # ty: ignore[invalid-argument-type]
|
||||
progress_callback=progress_handler or self._progress_handler,
|
||||
meta=meta,
|
||||
)
|
||||
return result
|
||||
|
||||
|
|
@ -869,6 +926,7 @@ class Client(Generic[ClientTransportT]):
|
|||
timeout: datetime.timedelta | float | int | None = None,
|
||||
progress_handler: ProgressHandler | None = None,
|
||||
raise_on_error: bool = True,
|
||||
meta: dict[str, Any] | None = None,
|
||||
) -> CallToolResult:
|
||||
"""Call a tool on the server.
|
||||
|
||||
|
|
@ -879,6 +937,11 @@ class Client(Generic[ClientTransportT]):
|
|||
arguments (dict[str, Any] | None, optional): Arguments to pass to the tool. Defaults to None.
|
||||
timeout (datetime.timedelta | float | int | None, optional): The timeout for the tool call. Defaults to None.
|
||||
progress_handler (ProgressHandler | None, optional): The progress handler to use for the tool call. Defaults to None.
|
||||
raise_on_error (bool, optional): Whether to raise a ToolError if the tool call results in an error. Defaults to True.
|
||||
meta (dict[str, Any] | None, optional): Additional metadata to include with the request.
|
||||
This is useful for passing contextual information (like user IDs, trace IDs, or preferences)
|
||||
that shouldn't be tool arguments but may influence server-side processing. The server
|
||||
can access this via `context.request_context.meta`. Defaults to None.
|
||||
|
||||
Returns:
|
||||
CallToolResult:
|
||||
|
|
@ -898,6 +961,7 @@ class Client(Generic[ClientTransportT]):
|
|||
arguments=arguments or {},
|
||||
timeout=timeout,
|
||||
progress_handler=progress_handler,
|
||||
meta=meta,
|
||||
)
|
||||
data = None
|
||||
if result.isError and raise_on_error:
|
||||
|
|
@ -928,6 +992,7 @@ class Client(Generic[ClientTransportT]):
|
|||
return CallToolResult(
|
||||
content=result.content,
|
||||
structured_content=result.structuredContent,
|
||||
meta=result.meta,
|
||||
data=data,
|
||||
is_error=result.isError,
|
||||
)
|
||||
|
|
@ -945,5 +1010,6 @@ class Client(Generic[ClientTransportT]):
|
|||
class CallToolResult:
|
||||
content: list[mcp.types.ContentBlock]
|
||||
structured_content: dict[str, Any] | None
|
||||
meta: dict[str, Any] | None
|
||||
data: Any = None
|
||||
is_error: bool = False
|
||||
|
|
|
|||
|
|
@ -177,8 +177,8 @@ class SSETransport(ClientTransport):
|
|||
|
||||
self.url = url
|
||||
self.headers = headers or {}
|
||||
self._set_auth(auth)
|
||||
self.httpx_client_factory = httpx_client_factory
|
||||
self._set_auth(auth)
|
||||
|
||||
if isinstance(sse_read_timeout, int | float):
|
||||
sse_read_timeout = datetime.timedelta(seconds=float(sse_read_timeout))
|
||||
|
|
@ -186,7 +186,7 @@ class SSETransport(ClientTransport):
|
|||
|
||||
def _set_auth(self, auth: httpx.Auth | Literal["oauth"] | str | None):
|
||||
if auth == "oauth":
|
||||
auth = OAuth(self.url)
|
||||
auth = OAuth(self.url, httpx_client_factory=self.httpx_client_factory)
|
||||
elif isinstance(auth, str):
|
||||
auth = BearerAuth(auth)
|
||||
self.auth = auth
|
||||
|
|
@ -247,8 +247,8 @@ class StreamableHttpTransport(ClientTransport):
|
|||
|
||||
self.url = url
|
||||
self.headers = headers or {}
|
||||
self._set_auth(auth)
|
||||
self.httpx_client_factory = httpx_client_factory
|
||||
self._set_auth(auth)
|
||||
|
||||
if isinstance(sse_read_timeout, int | float):
|
||||
sse_read_timeout = datetime.timedelta(seconds=float(sse_read_timeout))
|
||||
|
|
@ -256,7 +256,7 @@ class StreamableHttpTransport(ClientTransport):
|
|||
|
||||
def _set_auth(self, auth: httpx.Auth | Literal["oauth"] | str | None):
|
||||
if auth == "oauth":
|
||||
auth = OAuth(self.url)
|
||||
auth = OAuth(self.url, httpx_client_factory=self.httpx_client_factory)
|
||||
elif isinstance(auth, str):
|
||||
auth = BearerAuth(auth)
|
||||
self.auth = auth
|
||||
|
|
|
|||
|
|
@ -54,28 +54,27 @@ class RequestDirector:
|
|||
url = self._build_url(route.path, path_params, base_url)
|
||||
|
||||
# Step 3: Prepare request data
|
||||
request_data = {
|
||||
"method": route.method.upper(),
|
||||
"url": url,
|
||||
"params": query_params if query_params else None,
|
||||
"headers": header_params if header_params else None,
|
||||
}
|
||||
method: str = route.method.upper()
|
||||
params = query_params if query_params else None
|
||||
headers = header_params if header_params else None
|
||||
json_body: dict[str, Any] | list[Any] | None = None
|
||||
content: str | bytes | None = None
|
||||
|
||||
# Step 4: Handle request body
|
||||
if body is not None:
|
||||
if isinstance(body, dict | list):
|
||||
request_data["json"] = body
|
||||
json_body = body
|
||||
else:
|
||||
request_data["content"] = body
|
||||
content = body
|
||||
|
||||
# Step 5: Create httpx.Request
|
||||
return httpx.Request(
|
||||
method=request_data["method"],
|
||||
url=request_data["url"],
|
||||
params=request_data.get("params"),
|
||||
headers=request_data.get("headers"),
|
||||
json=request_data.get("json"),
|
||||
content=request_data.get("content"),
|
||||
method=method,
|
||||
url=url,
|
||||
params=params,
|
||||
headers=headers,
|
||||
json=json_body,
|
||||
content=content,
|
||||
)
|
||||
|
||||
def _unflatten_arguments(
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ class _TransformingMCPServerMixin(FastMCPBaseModel):
|
|||
ClientTransport, # pyright: ignore[reportUnusedImport]
|
||||
)
|
||||
|
||||
transport: ClientTransport = super().to_transport() # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue, reportUnknownVariableType]
|
||||
transport: ClientTransport = super().to_transport() # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue, reportUnknownVariableType] # ty: ignore[unresolved-attribute]
|
||||
transport = cast(ClientTransport, transport)
|
||||
|
||||
client: Client[ClientTransport] = Client(transport=transport, name=client_name)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
from mcp.server.auth.middleware.auth_context import AuthContextMiddleware
|
||||
from mcp.server.auth.middleware.bearer_auth import BearerAuthBackend
|
||||
|
|
@ -28,6 +28,10 @@ from starlette.middleware import Middleware
|
|||
from starlette.middleware.authentication import AuthenticationMiddleware
|
||||
from starlette.routing import Route
|
||||
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class AccessToken(_SDKAccessToken):
|
||||
"""AccessToken that includes all JWT claims."""
|
||||
|
|
@ -294,20 +298,27 @@ class OAuthProvider(
|
|||
required_scopes: Scopes that are required for all requests.
|
||||
"""
|
||||
|
||||
# Convert URLs to proper types
|
||||
if isinstance(base_url, str):
|
||||
base_url = AnyHttpUrl(base_url)
|
||||
|
||||
super().__init__(base_url=base_url, required_scopes=required_scopes)
|
||||
self.base_url = base_url
|
||||
|
||||
if issuer_url is None:
|
||||
self.issuer_url = base_url
|
||||
self.issuer_url = self.base_url
|
||||
elif isinstance(issuer_url, str):
|
||||
self.issuer_url = AnyHttpUrl(issuer_url)
|
||||
else:
|
||||
self.issuer_url = issuer_url
|
||||
|
||||
# Log if issuer_url and base_url differ (requires additional setup)
|
||||
if (
|
||||
self.base_url is not None
|
||||
and self.issuer_url is not None
|
||||
and str(self.base_url) != str(self.issuer_url)
|
||||
):
|
||||
logger.info(
|
||||
f"OAuth endpoints at {self.base_url}, issuer at {self.issuer_url}. "
|
||||
f"Ensure well-known routes are accessible at root ({self.issuer_url}/.well-known/). "
|
||||
f"See: https://gofastmcp.com/deployment/http#mounting-authenticated-servers"
|
||||
)
|
||||
|
||||
# Initialize OAuth Authorization Server Provider
|
||||
OAuthAuthorizationServerProvider.__init__(self)
|
||||
|
||||
|
|
@ -348,9 +359,17 @@ class OAuthProvider(
|
|||
"""
|
||||
|
||||
# Create standard OAuth authorization server routes
|
||||
# Pass base_url as issuer_url to ensure metadata declares endpoints where
|
||||
# they're actually accessible (operational routes are mounted at
|
||||
# base_url)
|
||||
assert self.base_url is not None # typing check
|
||||
assert (
|
||||
self.issuer_url is not None
|
||||
) # typing check (issuer_url defaults to base_url)
|
||||
|
||||
oauth_routes = create_auth_routes(
|
||||
provider=self,
|
||||
issuer_url=self.issuer_url,
|
||||
issuer_url=self.base_url,
|
||||
service_documentation_url=self.service_documentation_url,
|
||||
client_registration_options=self.client_registration_options,
|
||||
revocation_options=self.revocation_options,
|
||||
|
|
@ -369,7 +388,7 @@ class OAuthProvider(
|
|||
)
|
||||
protected_routes = create_protected_resource_routes(
|
||||
resource_url=resource_url,
|
||||
authorization_servers=[self.issuer_url],
|
||||
authorization_servers=[cast(AnyHttpUrl, self.issuer_url)],
|
||||
scopes_supported=supported_scopes,
|
||||
)
|
||||
oauth_routes.extend(protected_routes)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ The enhancement adds:
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from mcp.server.auth.handlers.authorize import (
|
||||
|
|
@ -211,12 +212,15 @@ class AuthorizationHandler(SDKAuthorizationHandler):
|
|||
# Check if this is a client not found error
|
||||
if response.status_code == 400:
|
||||
# Try to extract client_id from request for enhanced error
|
||||
client_id = None
|
||||
client_id: str | None = None
|
||||
if request.method == "GET":
|
||||
client_id = request.query_params.get("client_id")
|
||||
else:
|
||||
form = await request.form()
|
||||
client_id = form.get("client_id")
|
||||
client_id_value = form.get("client_id")
|
||||
# Ensure client_id is a string, not UploadFile
|
||||
if isinstance(client_id_value, str):
|
||||
client_id = client_id_value
|
||||
|
||||
# If we have a client_id and the error is about it not being found,
|
||||
# enhance the response
|
||||
|
|
@ -224,9 +228,7 @@ class AuthorizationHandler(SDKAuthorizationHandler):
|
|||
try:
|
||||
# Check if response body contains "not found" error
|
||||
if hasattr(response, "body"):
|
||||
import json
|
||||
|
||||
body = json.loads(response.body)
|
||||
body = json.loads(bytes(response.body))
|
||||
if (
|
||||
body.get("error") == "invalid_request"
|
||||
and "not found" in body.get("error_description", "").lower()
|
||||
|
|
|
|||
|
|
@ -309,10 +309,13 @@ def create_consent_html(
|
|||
"""
|
||||
|
||||
# Build form with buttons
|
||||
# Use empty action to submit to current URL (/consent or /mcp/consent)
|
||||
# The POST handler is registered at the same path as GET
|
||||
form = f"""
|
||||
<form id="consentForm" method="POST" action="/consent/submit">
|
||||
<form id="consentForm" method="POST" action="">
|
||||
<input type="hidden" name="txn_id" value="{txn_id}" />
|
||||
<input type="hidden" name="csrf_token" value="{csrf_token}" />
|
||||
<input type="hidden" name="submit" value="true" />
|
||||
<div class="button-group">
|
||||
<button type="submit" name="action" value="approve" class="btn-approve">Allow Access</button>
|
||||
<button type="submit" name="action" value="deny" class="btn-deny">Deny</button>
|
||||
|
|
@ -1605,9 +1608,10 @@ class OAuthProxy(OAuthProvider):
|
|||
):
|
||||
authorize_route_found = True
|
||||
# Replace with our enhanced authorization handler
|
||||
# Note: self.base_url is guaranteed to be set in parent __init__
|
||||
authorize_handler = AuthorizationHandler(
|
||||
provider=self,
|
||||
base_url=self.base_url,
|
||||
base_url=self.base_url, # ty: ignore[invalid-argument-type]
|
||||
server_name=None, # Could be extended to pass server metadata
|
||||
server_icon_url=None,
|
||||
)
|
||||
|
|
@ -1653,12 +1657,10 @@ class OAuthProxy(OAuthProvider):
|
|||
)
|
||||
|
||||
# Add consent endpoints
|
||||
custom_routes.append(
|
||||
Route(path="/consent", endpoint=self._show_consent_page, methods=["GET"])
|
||||
)
|
||||
# Handle both GET (show page) and POST (submit) at /consent
|
||||
custom_routes.append(
|
||||
Route(
|
||||
path="/consent/submit", endpoint=self._submit_consent, methods=["POST"]
|
||||
path="/consent", endpoint=self._handle_consent, methods=["GET", "POST"]
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -1973,6 +1975,14 @@ class OAuthProxy(OAuthProvider):
|
|||
separator = "&" if "?" in self._upstream_authorization_endpoint else "?"
|
||||
return f"{self._upstream_authorization_endpoint}{separator}{urlencode(query_params)}"
|
||||
|
||||
async def _handle_consent(
|
||||
self, request: Request
|
||||
) -> HTMLResponse | RedirectResponse:
|
||||
"""Handle consent page - dispatch to GET or POST handler based on method."""
|
||||
if request.method == "POST":
|
||||
return await self._submit_consent(request)
|
||||
return await self._show_consent_page(request)
|
||||
|
||||
async def _show_consent_page(
|
||||
self, request: Request
|
||||
) -> HTMLResponse | RedirectResponse:
|
||||
|
|
|
|||
|
|
@ -340,7 +340,7 @@ class OIDCProxy(OAuthProxy):
|
|||
init_kwargs["extra_authorize_params"] = extra_params
|
||||
init_kwargs["extra_token_params"] = extra_params
|
||||
|
||||
super().__init__(**init_kwargs)
|
||||
super().__init__(**init_kwargs) # ty: ignore[invalid-argument-type]
|
||||
|
||||
def get_oidc_configuration(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ class DescopeProvider(RemoteAuthProvider):
|
|||
)
|
||||
|
||||
self.project_id = settings.project_id
|
||||
self.base_url = str(settings.base_url).rstrip("/")
|
||||
self.base_url = AnyHttpUrl(str(settings.base_url).rstrip("/"))
|
||||
self.descope_base_url = str(settings.descope_base_url).rstrip("/")
|
||||
|
||||
# Create default JWT verifier if none provided
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ for seamless MCP client authentication.
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Literal
|
||||
|
||||
import httpx
|
||||
from pydantic import AnyHttpUrl, field_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
|
@ -32,6 +34,7 @@ class SupabaseProviderSettings(BaseSettings):
|
|||
|
||||
project_url: AnyHttpUrl
|
||||
base_url: AnyHttpUrl
|
||||
algorithm: Literal["HS256", "RS256", "ES256"] = "ES256"
|
||||
required_scopes: list[str] | None = None
|
||||
|
||||
@field_validator("required_scopes", mode="before")
|
||||
|
|
@ -52,13 +55,19 @@ class SupabaseProvider(RemoteAuthProvider):
|
|||
1. Supabase Project Setup:
|
||||
- Create a Supabase project at https://supabase.com
|
||||
- Note your project URL (e.g., "https://abc123.supabase.co")
|
||||
- For projects created after May 1st, 2025, asymmetric RS256 keys are used by default
|
||||
- For older projects, consider migrating to asymmetric keys for better security
|
||||
- Configure your JWT algorithm in Supabase Auth settings (HS256, RS256, or ES256)
|
||||
- Asymmetric keys (RS256/ES256) are recommended for production
|
||||
|
||||
2. JWT Verification:
|
||||
- FastMCP verifies JWTs using the JWKS endpoint at {project_url}/auth/v1/.well-known/jwks.json
|
||||
- JWTs are issued by {project_url}/auth/v1
|
||||
- Tokens are cached for up to 10 minutes by Supabase's edge servers
|
||||
- Algorithm must match your Supabase Auth configuration
|
||||
|
||||
3. Authorization:
|
||||
- Supabase uses Row Level Security (RLS) policies for database authorization
|
||||
- OAuth-level scopes are an upcoming feature in Supabase Auth
|
||||
- Both approaches will be supported once scope handling is available
|
||||
|
||||
For detailed setup instructions, see:
|
||||
https://supabase.com/docs/guides/auth/jwts
|
||||
|
|
@ -71,6 +80,7 @@ class SupabaseProvider(RemoteAuthProvider):
|
|||
supabase_auth = SupabaseProvider(
|
||||
project_url="https://abc123.supabase.co",
|
||||
base_url="https://your-fastmcp-server.com",
|
||||
algorithm="ES256", # Match your Supabase Auth configuration
|
||||
)
|
||||
|
||||
# Use with FastMCP
|
||||
|
|
@ -83,6 +93,7 @@ class SupabaseProvider(RemoteAuthProvider):
|
|||
*,
|
||||
project_url: AnyHttpUrl | str | NotSetT = NotSet,
|
||||
base_url: AnyHttpUrl | str | NotSetT = NotSet,
|
||||
algorithm: Literal["HS256", "RS256", "ES256"] | NotSetT = NotSet,
|
||||
required_scopes: list[str] | NotSetT | None = NotSet,
|
||||
token_verifier: TokenVerifier | None = None,
|
||||
):
|
||||
|
|
@ -91,7 +102,11 @@ class SupabaseProvider(RemoteAuthProvider):
|
|||
Args:
|
||||
project_url: Your Supabase project URL (e.g., "https://abc123.supabase.co")
|
||||
base_url: Public URL of this FastMCP server
|
||||
required_scopes: Optional list of scopes to require for all requests
|
||||
algorithm: JWT signing algorithm (HS256, RS256, or ES256). Must match your
|
||||
Supabase Auth configuration. Defaults to ES256.
|
||||
required_scopes: Optional list of scopes to require for all requests.
|
||||
Note: Supabase currently uses RLS policies for authorization. OAuth-level
|
||||
scopes are an upcoming feature.
|
||||
token_verifier: Optional token verifier. If None, creates JWT verifier for Supabase
|
||||
"""
|
||||
settings = SupabaseProviderSettings.model_validate(
|
||||
|
|
@ -100,6 +115,7 @@ class SupabaseProvider(RemoteAuthProvider):
|
|||
for k, v in {
|
||||
"project_url": project_url,
|
||||
"base_url": base_url,
|
||||
"algorithm": algorithm,
|
||||
"required_scopes": required_scopes,
|
||||
}.items()
|
||||
if v is not NotSet
|
||||
|
|
@ -107,14 +123,14 @@ class SupabaseProvider(RemoteAuthProvider):
|
|||
)
|
||||
|
||||
self.project_url = str(settings.project_url).rstrip("/")
|
||||
self.base_url = str(settings.base_url).rstrip("/")
|
||||
self.base_url = AnyHttpUrl(str(settings.base_url).rstrip("/"))
|
||||
|
||||
# Create default JWT verifier if none provided
|
||||
if token_verifier is None:
|
||||
token_verifier = JWTVerifier(
|
||||
jwks_uri=f"{self.project_url}/auth/v1/.well-known/jwks.json",
|
||||
issuer=f"{self.project_url}/auth/v1",
|
||||
algorithm="ES256", # Supabase uses ES256 for asymmetric keys
|
||||
algorithm=settings.algorithm,
|
||||
required_scopes=settings.required_scopes,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -362,7 +362,7 @@ class AuthKitProvider(RemoteAuthProvider):
|
|||
)
|
||||
|
||||
self.authkit_domain = str(settings.authkit_domain).rstrip("/")
|
||||
self.base_url = str(settings.base_url).rstrip("/")
|
||||
self.base_url = AnyHttpUrl(str(settings.base_url).rstrip("/"))
|
||||
|
||||
# Create default JWT verifier if none provided
|
||||
if token_verifier is None:
|
||||
|
|
|
|||
|
|
@ -9,9 +9,11 @@ from mcp.server.auth.middleware.auth_context import (
|
|||
from mcp.server.auth.provider import (
|
||||
AccessToken as _SDKAccessToken,
|
||||
)
|
||||
from mcp.server.lowlevel.server import request_ctx
|
||||
from starlette.requests import Request
|
||||
|
||||
from fastmcp.server.auth import AccessToken
|
||||
from fastmcp.server.http import _current_http_request
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from fastmcp.server.context import Context
|
||||
|
|
@ -41,12 +43,16 @@ def get_context() -> Context:
|
|||
|
||||
|
||||
def get_http_request() -> Request:
|
||||
from mcp.server.lowlevel.server import request_ctx
|
||||
|
||||
# Try MCP SDK's request_ctx first (set during normal MCP request handling)
|
||||
request = None
|
||||
with contextlib.suppress(LookupError):
|
||||
request = request_ctx.get().request
|
||||
|
||||
# Fallback to FastMCP's HTTP context variable
|
||||
# This is needed during `on_initialize` middleware where request_ctx isn't set yet
|
||||
if request is None:
|
||||
request = _current_http_request.get()
|
||||
|
||||
if request is None:
|
||||
raise RuntimeError("No active HTTP request found.")
|
||||
return request
|
||||
|
|
|
|||
|
|
@ -63,14 +63,21 @@ class CachableReadResourceContents(BaseModel):
|
|||
class CachableToolResult(BaseModel):
|
||||
content: list[mcp.types.ContentBlock]
|
||||
structured_content: dict[str, Any] | None
|
||||
meta: dict[str, Any] | None
|
||||
|
||||
@classmethod
|
||||
def wrap(cls, value: ToolResult) -> Self:
|
||||
return cls(content=value.content, structured_content=value.structured_content)
|
||||
return cls(
|
||||
content=value.content,
|
||||
structured_content=value.structured_content,
|
||||
meta=value.meta,
|
||||
)
|
||||
|
||||
def unwrap(self) -> ToolResult:
|
||||
return ToolResult(
|
||||
content=self.content, structured_content=self.structured_content
|
||||
content=self.content,
|
||||
structured_content=self.structured_content,
|
||||
meta=self.meta,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class BaseLoggingMiddleware(Middleware):
|
|||
|
||||
return payload
|
||||
|
||||
def _format_message(self, message: dict[str, str | int]) -> str:
|
||||
def _format_message(self, message: dict[str, str | int | float]) -> str:
|
||||
"""Format a message for logging."""
|
||||
if self.structured_logging:
|
||||
return json.dumps(message)
|
||||
|
|
@ -55,7 +55,7 @@ class BaseLoggingMiddleware(Middleware):
|
|||
|
||||
def _create_before_message(
|
||||
self, context: MiddlewareContext[Any]
|
||||
) -> dict[str, str | int]:
|
||||
) -> dict[str, str | int | float]:
|
||||
message = {
|
||||
"event": context.type + "_start",
|
||||
"method": context.method or "unknown",
|
||||
|
|
|
|||
|
|
@ -149,8 +149,8 @@ class Middleware:
|
|||
|
||||
async def on_initialize(
|
||||
self,
|
||||
context: MiddlewareContext[mt.InitializeRequestParams],
|
||||
call_next: CallNext[mt.InitializeRequestParams, None],
|
||||
context: MiddlewareContext[mt.InitializeRequest],
|
||||
call_next: CallNext[mt.InitializeRequest, None],
|
||||
) -> None:
|
||||
return await call_next(context)
|
||||
|
||||
|
|
|
|||
|
|
@ -511,7 +511,7 @@ class ProxyClient(Client[ClientTransportT]):
|
|||
def __init__(
|
||||
self,
|
||||
transport: ClientTransportT
|
||||
| FastMCP
|
||||
| FastMCP[Any]
|
||||
| FastMCP1Server
|
||||
| AnyUrl
|
||||
| Path
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ from fastmcp.utilities.types import NotSet, NotSetT
|
|||
|
||||
if TYPE_CHECKING:
|
||||
from fastmcp.client import Client
|
||||
from fastmcp.client.client import FastMCP1Server
|
||||
from fastmcp.client.transports import ClientTransport, ClientTransportT
|
||||
from fastmcp.experimental.server.openapi import FastMCPOpenAPI as FastMCPOpenAPINew
|
||||
from fastmcp.experimental.server.openapi.routing import (
|
||||
|
|
@ -1008,7 +1009,11 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
|
||||
async def _call_tool_mcp(
|
||||
self, key: str, arguments: dict[str, Any]
|
||||
) -> list[ContentBlock] | tuple[list[ContentBlock], dict[str, Any]]:
|
||||
) -> (
|
||||
list[ContentBlock]
|
||||
| tuple[list[ContentBlock], dict[str, Any]]
|
||||
| mcp.types.CallToolResult
|
||||
):
|
||||
"""
|
||||
Handle MCP 'callTool' requests.
|
||||
|
||||
|
|
@ -1447,7 +1452,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
icons=icons,
|
||||
tags=tags,
|
||||
output_schema=output_schema,
|
||||
annotations=cast(ToolAnnotations | None, annotations),
|
||||
annotations=annotations,
|
||||
exclude_args=exclude_args,
|
||||
meta=meta,
|
||||
serializer=self._tool_serializer,
|
||||
|
|
@ -1642,7 +1647,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
mime_type=mime_type,
|
||||
tags=tags,
|
||||
enabled=enabled,
|
||||
annotations=cast(Annotations | None, annotations),
|
||||
annotations=annotations,
|
||||
meta=meta,
|
||||
)
|
||||
self.add_template(template)
|
||||
|
|
@ -1658,7 +1663,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
mime_type=mime_type,
|
||||
tags=tags,
|
||||
enabled=enabled,
|
||||
annotations=cast(Annotations | None, annotations),
|
||||
annotations=annotations,
|
||||
meta=meta,
|
||||
)
|
||||
self.add_resource(resource)
|
||||
|
|
@ -2393,6 +2398,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
Client[ClientTransportT]
|
||||
| ClientTransport
|
||||
| FastMCP[Any]
|
||||
| FastMCP1Server
|
||||
| AnyUrl
|
||||
| Path
|
||||
| MCPConfig
|
||||
|
|
@ -2435,7 +2441,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
|
||||
client_factory = fresh_client_factory
|
||||
else:
|
||||
base_client = ProxyClient(backend)
|
||||
base_client = ProxyClient(backend) # type: ignore
|
||||
|
||||
# Fresh client created from transport - use fresh sessions per request
|
||||
def proxy_client_factory():
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from typing import (
|
|||
|
||||
import mcp.types
|
||||
import pydantic_core
|
||||
from mcp.types import ContentBlock, Icon, TextContent, ToolAnnotations
|
||||
from mcp.types import CallToolResult, ContentBlock, Icon, TextContent, ToolAnnotations
|
||||
from mcp.types import Tool as MCPTool
|
||||
from pydantic import Field, PydanticSchemaGenerationError
|
||||
from typing_extensions import TypeVar
|
||||
|
|
@ -65,6 +65,7 @@ class ToolResult:
|
|||
self,
|
||||
content: list[ContentBlock] | Any | None = None,
|
||||
structured_content: dict[str, Any] | Any | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
):
|
||||
if content is None and structured_content is None:
|
||||
raise ValueError("Either content or structured_content must be provided")
|
||||
|
|
@ -72,6 +73,7 @@ class ToolResult:
|
|||
content = structured_content
|
||||
|
||||
self.content: list[ContentBlock] = _convert_to_content(result=content)
|
||||
self.meta: dict[str, Any] | None = meta
|
||||
|
||||
if structured_content is not None:
|
||||
try:
|
||||
|
|
@ -93,7 +95,15 @@ class ToolResult:
|
|||
|
||||
def to_mcp_result(
|
||||
self,
|
||||
) -> list[ContentBlock] | tuple[list[ContentBlock], dict[str, Any]]:
|
||||
) -> (
|
||||
list[ContentBlock] | tuple[list[ContentBlock], dict[str, Any]] | CallToolResult
|
||||
):
|
||||
if self.meta is not None:
|
||||
return CallToolResult(
|
||||
structuredContent=self.structured_content,
|
||||
content=self.content,
|
||||
_meta=self.meta,
|
||||
)
|
||||
if self.structured_content is None:
|
||||
return self.content
|
||||
return self.content, self.structured_content
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Annotated, Any, TypedDict
|
||||
from typing import Annotated, Any, TypedDict, cast
|
||||
|
||||
from mcp.types import Icon
|
||||
from pydantic import BeforeValidator, Field, PrivateAttr
|
||||
|
|
@ -117,7 +117,7 @@ class FastMCPComponent(FastMCPBaseModel):
|
|||
copy = super().model_copy(update=update, deep=deep)
|
||||
if key is not None:
|
||||
copy._key = key
|
||||
return copy
|
||||
return cast(Self, copy)
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if type(self) is not type(other):
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ def _single_pass_optimize(
|
|||
if isinstance(node, dict):
|
||||
# Collect $ref references for unused definition removal
|
||||
if prune_defs:
|
||||
ref = node.get("$ref")
|
||||
ref = node.get("$ref") # type: ignore
|
||||
if isinstance(ref, str) and ref.startswith("#/$defs/"):
|
||||
referenced_def = ref.split("/")[-1]
|
||||
if current_def_name:
|
||||
|
|
@ -127,13 +127,13 @@ def _single_pass_optimize(
|
|||
"required",
|
||||
]
|
||||
):
|
||||
node.pop("title")
|
||||
node.pop("title") # type: ignore
|
||||
|
||||
if (
|
||||
prune_additional_properties
|
||||
and node.get("additionalProperties") is False
|
||||
and node.get("additionalProperties") is False # type: ignore
|
||||
):
|
||||
node.pop("additionalProperties")
|
||||
node.pop("additionalProperties") # type: ignore
|
||||
|
||||
# Recursive traversal
|
||||
for key, value in node.items():
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ class MCPServerConfig(BaseModel):
|
|||
"""
|
||||
if isinstance(v, dict):
|
||||
return Deployment(**v) # type: ignore[arg-type]
|
||||
return cast(Deployment, v)
|
||||
return cast(Deployment, v) # type: ignore[return-value]
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, file_path: Path) -> MCPServerConfig:
|
||||
|
|
|
|||
|
|
@ -1371,7 +1371,7 @@ def _combine_schemas(route: HTTPRoute) -> dict[str, Any]:
|
|||
if used_refs:
|
||||
result["$defs"] = {
|
||||
name: def_schema
|
||||
for name, def_schema in result["$defs"].items()
|
||||
for name, def_schema in result["$defs"].items() # type: ignore[index]
|
||||
if name in used_refs
|
||||
}
|
||||
else:
|
||||
|
|
@ -1556,7 +1556,7 @@ def extract_output_schema_from_responses(
|
|||
if used_refs:
|
||||
output_schema["$defs"] = {
|
||||
name: def_schema
|
||||
for name, def_schema in output_schema["$defs"].items()
|
||||
for name, def_schema in output_schema["$defs"].items() # type: ignore[index]
|
||||
if name in used_refs
|
||||
}
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -69,13 +69,13 @@ class TestCursorDeeplinkGeneration:
|
|||
args=["run", "--with", "fastmcp", "fastmcp", "run", "server.py"],
|
||||
)
|
||||
|
||||
# Test with spaces and special chars in name
|
||||
# Test with spaces and special chars in name - should be URL encoded
|
||||
deeplink = generate_cursor_deeplink("my server (test)", server_config)
|
||||
|
||||
assert (
|
||||
"name=my%20server%20%28test%29" in deeplink
|
||||
or "name=my server (test)" in deeplink
|
||||
)
|
||||
# Spaces and parentheses must be URL-encoded
|
||||
assert "name=my%20server%20%28test%29" in deeplink
|
||||
# Ensure no unencoded version appears
|
||||
assert "name=my server (test)" not in deeplink
|
||||
|
||||
def test_generate_deeplink_empty_config(self):
|
||||
"""Test deeplink generation with minimal config."""
|
||||
|
|
@ -118,6 +118,48 @@ class TestCursorDeeplinkGeneration:
|
|||
assert "--with-editable" in config_data["args"]
|
||||
assert "server.py:CustomServer" in config_data["args"]
|
||||
|
||||
def test_generate_deeplink_url_injection_protection(self):
|
||||
"""Test that special characters in server name are properly URL-encoded to prevent injection."""
|
||||
server_config = StdioMCPServer(
|
||||
command="python",
|
||||
args=["server.py"],
|
||||
)
|
||||
|
||||
# Test the PoC case from the security advisory
|
||||
deeplink = generate_cursor_deeplink("test&calc", server_config)
|
||||
|
||||
# The & should be encoded as %26, preventing it from being interpreted as a query parameter separator
|
||||
assert "name=test%26calc" in deeplink
|
||||
assert "name=test&calc" not in deeplink
|
||||
|
||||
# Verify the URL structure is intact
|
||||
assert deeplink.startswith("cursor://anysphere.cursor-deeplink/mcp/install?")
|
||||
assert deeplink.count("&") == 1 # Only one & between name and config parameters
|
||||
|
||||
# Test other potentially dangerous characters
|
||||
dangerous_names = [
|
||||
("test|calc", "test%7Ccalc"),
|
||||
("test;calc", "test%3Bcalc"),
|
||||
("test<calc", "test%3Ccalc"),
|
||||
("test>calc", "test%3Ecalc"),
|
||||
("test`calc", "test%60calc"),
|
||||
("test$calc", "test%24calc"),
|
||||
("test'calc", "test%27calc"),
|
||||
('test"calc', "test%22calc"),
|
||||
("test calc", "test%20calc"),
|
||||
("test#anchor", "test%23anchor"),
|
||||
("test?query=val", "test%3Fquery%3Dval"),
|
||||
]
|
||||
|
||||
for dangerous_name, expected_encoded in dangerous_names:
|
||||
deeplink = generate_cursor_deeplink(dangerous_name, server_config)
|
||||
assert f"name={expected_encoded}" in deeplink, (
|
||||
f"Failed to encode {dangerous_name}"
|
||||
)
|
||||
# Ensure no unencoded special chars that could break URL structure
|
||||
name_part = deeplink.split("name=")[1].split("&")[0]
|
||||
assert name_part == expected_encoded
|
||||
|
||||
|
||||
class TestOpenDeeplink:
|
||||
"""Test deeplink opening functionality."""
|
||||
|
|
@ -135,18 +177,16 @@ class TestOpenDeeplink:
|
|||
["open", "cursor://test"], check=True, capture_output=True
|
||||
)
|
||||
|
||||
@patch("subprocess.run")
|
||||
def test_open_deeplink_windows(self, mock_run):
|
||||
def test_open_deeplink_windows(self):
|
||||
"""Test opening deeplink on Windows."""
|
||||
with patch("sys.platform", "win32"):
|
||||
mock_run.return_value = Mock(returncode=0)
|
||||
with patch(
|
||||
"fastmcp.cli.install.cursor.os.startfile", create=True
|
||||
) as mock_startfile:
|
||||
result = open_deeplink("cursor://test")
|
||||
|
||||
result = open_deeplink("cursor://test")
|
||||
|
||||
assert result is True
|
||||
mock_run.assert_called_once_with(
|
||||
["cmd", "/c", "start", "cursor://test"], check=True, capture_output=True
|
||||
)
|
||||
assert result is True
|
||||
mock_startfile.assert_called_once_with("cursor://test")
|
||||
|
||||
@patch("subprocess.run")
|
||||
def test_open_deeplink_linux(self, mock_run):
|
||||
|
|
@ -166,21 +206,57 @@ class TestOpenDeeplink:
|
|||
"""Test handling of deeplink opening failure."""
|
||||
import subprocess
|
||||
|
||||
mock_run.side_effect = subprocess.CalledProcessError(1, ["open"])
|
||||
with patch("sys.platform", "darwin"):
|
||||
mock_run.side_effect = subprocess.CalledProcessError(1, ["open"])
|
||||
|
||||
result = open_deeplink("cursor://test")
|
||||
result = open_deeplink("cursor://test")
|
||||
|
||||
assert result is False
|
||||
assert result is False
|
||||
|
||||
@patch("subprocess.run")
|
||||
def test_open_deeplink_command_not_found(self, mock_run):
|
||||
"""Test handling when open command is not found."""
|
||||
mock_run.side_effect = FileNotFoundError()
|
||||
with patch("sys.platform", "darwin"):
|
||||
mock_run.side_effect = FileNotFoundError()
|
||||
|
||||
result = open_deeplink("cursor://test")
|
||||
result = open_deeplink("cursor://test")
|
||||
|
||||
assert result is False
|
||||
|
||||
def test_open_deeplink_invalid_scheme(self):
|
||||
"""Test that non-cursor:// URLs are rejected."""
|
||||
result = open_deeplink("http://malicious.com")
|
||||
assert result is False
|
||||
|
||||
result = open_deeplink("https://example.com")
|
||||
assert result is False
|
||||
|
||||
result = open_deeplink("file:///etc/passwd")
|
||||
assert result is False
|
||||
|
||||
def test_open_deeplink_valid_cursor_scheme(self):
|
||||
"""Test that cursor:// URLs are accepted."""
|
||||
with patch("sys.platform", "darwin"):
|
||||
with patch("subprocess.run") as mock_run:
|
||||
mock_run.return_value = Mock(returncode=0)
|
||||
result = open_deeplink("cursor://anysphere.cursor-deeplink/mcp/install")
|
||||
assert result is True
|
||||
|
||||
def test_open_deeplink_empty_url(self):
|
||||
"""Test handling of empty URL."""
|
||||
result = open_deeplink("")
|
||||
assert result is False
|
||||
|
||||
def test_open_deeplink_windows_oserror(self):
|
||||
"""Test handling of OSError on Windows."""
|
||||
with patch("sys.platform", "win32"):
|
||||
with patch(
|
||||
"fastmcp.cli.install.cursor.os.startfile", create=True
|
||||
) as mock_startfile:
|
||||
mock_startfile.side_effect = OSError("File not found")
|
||||
result = open_deeplink("cursor://test")
|
||||
assert result is False
|
||||
|
||||
|
||||
class TestInstallCursor:
|
||||
"""Test cursor installation functionality."""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import asyncio
|
||||
import sys
|
||||
from typing import cast
|
||||
from typing import Any, cast
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import mcp
|
||||
|
|
@ -148,6 +148,46 @@ async def test_call_tool_mcp(fastmcp_server):
|
|||
assert "Hello, World!" in content_str
|
||||
|
||||
|
||||
async def test_call_tool_with_meta():
|
||||
"""Test that meta parameter is properly passed from client to server."""
|
||||
server = FastMCP("MetaTestServer")
|
||||
|
||||
# Create a tool that accesses the meta from the request context
|
||||
@server.tool
|
||||
def check_meta() -> dict[str, Any]:
|
||||
"""A tool that returns the meta from the request context."""
|
||||
from fastmcp.server.dependencies import get_context
|
||||
|
||||
context = get_context()
|
||||
meta = context.request_context.meta
|
||||
|
||||
# Return the meta data as a dict
|
||||
if meta is not None:
|
||||
return {
|
||||
"has_meta": True,
|
||||
"user_id": getattr(meta, "user_id", None),
|
||||
"trace_id": getattr(meta, "trace_id", None),
|
||||
}
|
||||
return {"has_meta": False}
|
||||
|
||||
client = Client(transport=FastMCPTransport(server))
|
||||
|
||||
async with client:
|
||||
# Test with meta parameter - verify the server receives it
|
||||
test_meta = {"user_id": "test-123", "trace_id": "abc-def"}
|
||||
result = await client.call_tool("check_meta", {}, meta=test_meta)
|
||||
|
||||
assert result.data["has_meta"] is True
|
||||
assert result.data["user_id"] == "test-123"
|
||||
assert result.data["trace_id"] == "abc-def"
|
||||
|
||||
# Test without meta parameter - verify fields are not present
|
||||
result_no_meta = await client.call_tool("check_meta", {})
|
||||
# When meta is not provided, custom fields should not be present
|
||||
assert result_no_meta.data.get("user_id") is None
|
||||
assert result_no_meta.data.get("trace_id") is None
|
||||
|
||||
|
||||
async def test_list_resources(fastmcp_server):
|
||||
"""Test listing resources with InMemoryClient."""
|
||||
client = Client(transport=FastMCPTransport(fastmcp_server))
|
||||
|
|
@ -386,9 +426,8 @@ async def test_initialize_result_connected(fastmcp_server):
|
|||
"""Test that initialize_result returns the correct result when connected."""
|
||||
client = Client(transport=FastMCPTransport(fastmcp_server))
|
||||
|
||||
# Initialize result should not be accessible before connection
|
||||
with pytest.raises(RuntimeError, match="Client is not connected"):
|
||||
_ = client.initialize_result
|
||||
# Initialize result should be None before connection
|
||||
assert client.initialize_result is None
|
||||
|
||||
async with client:
|
||||
# Once connected, initialize_result should be available
|
||||
|
|
@ -401,21 +440,19 @@ async def test_initialize_result_connected(fastmcp_server):
|
|||
|
||||
|
||||
async def test_initialize_result_disconnected(fastmcp_server):
|
||||
"""Test that initialize_result raises an error when not connected."""
|
||||
"""Test that initialize_result is None when not connected."""
|
||||
client = Client(transport=FastMCPTransport(fastmcp_server))
|
||||
|
||||
# Initialize result should not be accessible before connection
|
||||
with pytest.raises(RuntimeError, match="Client is not connected"):
|
||||
_ = client.initialize_result
|
||||
# Initialize result should be None before connection
|
||||
assert client.initialize_result is None
|
||||
|
||||
# Connect and then disconnect
|
||||
async with client:
|
||||
assert client.is_connected()
|
||||
|
||||
# After disconnection, initialize_result should raise an error
|
||||
# After disconnection, initialize_result should be None again
|
||||
assert not client.is_connected()
|
||||
with pytest.raises(RuntimeError, match="Client is not connected"):
|
||||
_ = client.initialize_result
|
||||
assert client.initialize_result is None
|
||||
|
||||
|
||||
async def test_server_info_custom_version():
|
||||
|
|
@ -1026,3 +1063,111 @@ class TestAuth:
|
|||
assert isinstance(client.transport, SSETransport)
|
||||
assert isinstance(client.transport.auth, BearerAuth)
|
||||
assert client.transport.auth.token.get_secret_value() == "test_token"
|
||||
|
||||
|
||||
class TestInitialize:
|
||||
"""Tests for client initialization behavior."""
|
||||
|
||||
async def test_auto_initialize_default(self, fastmcp_server):
|
||||
"""Test that auto_initialize=True is the default and works automatically."""
|
||||
client = Client(fastmcp_server)
|
||||
|
||||
async with client:
|
||||
# Should be automatically initialized
|
||||
assert client.initialize_result is not None
|
||||
assert client.initialize_result.serverInfo.name == "TestServer"
|
||||
assert client.initialize_result.instructions is None
|
||||
|
||||
async def test_auto_initialize_explicit_true(self, fastmcp_server):
|
||||
"""Test explicit auto_initialize=True."""
|
||||
client = Client(fastmcp_server, auto_initialize=True)
|
||||
|
||||
async with client:
|
||||
assert client.initialize_result is not None
|
||||
assert client.initialize_result.serverInfo.name == "TestServer"
|
||||
|
||||
async def test_auto_initialize_false(self, fastmcp_server):
|
||||
"""Test that auto_initialize=False prevents automatic initialization."""
|
||||
client = Client(fastmcp_server, auto_initialize=False)
|
||||
|
||||
async with client:
|
||||
# Should not be automatically initialized
|
||||
assert client.initialize_result is None
|
||||
|
||||
async def test_manual_initialize(self, fastmcp_server):
|
||||
"""Test manual initialization when auto_initialize=False."""
|
||||
client = Client(fastmcp_server, auto_initialize=False)
|
||||
|
||||
async with client:
|
||||
# Manually initialize
|
||||
result = await client.initialize()
|
||||
|
||||
assert result is not None
|
||||
assert result.serverInfo.name == "TestServer"
|
||||
assert client.initialize_result is result
|
||||
|
||||
async def test_initialize_idempotent(self, fastmcp_server):
|
||||
"""Test that calling initialize() multiple times returns cached result."""
|
||||
client = Client(fastmcp_server, auto_initialize=False)
|
||||
|
||||
async with client:
|
||||
result1 = await client.initialize()
|
||||
result2 = await client.initialize()
|
||||
result3 = await client.initialize()
|
||||
|
||||
# All should return the same cached result
|
||||
assert result1 is result2
|
||||
assert result2 is result3
|
||||
|
||||
async def test_initialize_with_instructions(self):
|
||||
"""Test that server instructions are available via initialize_result."""
|
||||
server = FastMCP("InstructionsServer", instructions="Use the greet tool!")
|
||||
|
||||
@server.tool
|
||||
def greet(name: str) -> str:
|
||||
return f"Hello, {name}!"
|
||||
|
||||
client = Client(server)
|
||||
|
||||
async with client:
|
||||
assert client.initialize_result.instructions == "Use the greet tool!"
|
||||
|
||||
async def test_initialize_timeout_custom(self, fastmcp_server):
|
||||
"""Test custom timeout for initialize()."""
|
||||
client = Client(fastmcp_server, auto_initialize=False)
|
||||
|
||||
async with client:
|
||||
# Should succeed with reasonable timeout
|
||||
result = await client.initialize(timeout=5.0)
|
||||
assert result is not None
|
||||
|
||||
async def test_initialize_property_after_auto_init(self, fastmcp_server):
|
||||
"""Test accessing initialize_result property after auto-initialization."""
|
||||
client = Client(fastmcp_server, auto_initialize=True)
|
||||
|
||||
async with client:
|
||||
# Access via property
|
||||
result = client.initialize_result
|
||||
assert result.serverInfo.name == "TestServer"
|
||||
|
||||
# Call method - should return cached
|
||||
result2 = await client.initialize()
|
||||
assert result is result2
|
||||
|
||||
async def test_initialize_property_before_connect(self, fastmcp_server):
|
||||
"""Test that initialize_result property is None before connection."""
|
||||
client = Client(fastmcp_server)
|
||||
|
||||
# Not yet connected
|
||||
assert client.initialize_result is None
|
||||
|
||||
async def test_manual_initialize_can_call_tools(self, fastmcp_server):
|
||||
"""Test that manually initialized client can call tools."""
|
||||
client = Client(fastmcp_server, auto_initialize=False)
|
||||
|
||||
async with client:
|
||||
await client.initialize()
|
||||
|
||||
# Should be able to call tools after manual initialization
|
||||
result = await client.call_tool("greet", {"name": "World"})
|
||||
assert "Hello, World!" in str(result.content)
|
||||
|
|
|
|||
|
|
@ -417,9 +417,9 @@ async def test_structured_response_type(
|
|||
|
||||
if result.action == "accept":
|
||||
if isinstance(result.data, dict):
|
||||
return f"User: {result.data['name']}, age: {result.data['age']}"
|
||||
return f"User: {result.data['name']}, age: {result.data['age']}" # type: ignore[index]
|
||||
else:
|
||||
return f"User: {result.data.name}, age: {result.data.age}"
|
||||
return f"User: {result.data.name}, age: {result.data.age}" # type: ignore[attr-defined]
|
||||
return "No user info provided"
|
||||
|
||||
async def elicitation_handler(message, response_type, params, ctx):
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ def create_test_server() -> FastMCP:
|
|||
result = await ctx.elicit("What is your name?", response_type=str)
|
||||
|
||||
if result.action == "accept":
|
||||
return f"You said your name was: {result.data}!" # ty: ignore[possibly-unbound-attribute]
|
||||
return f"You said your name was: {result.data}!" # ty: ignore[possibly-missing-attribute]
|
||||
else:
|
||||
return "No name provided"
|
||||
|
||||
|
|
|
|||
37
tests/client/transports/test_transports.py
Normal file
37
tests/client/transports/test_transports.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
from ssl import VerifyMode
|
||||
|
||||
import httpx
|
||||
|
||||
from fastmcp.client.transports import SSETransport, StreamableHttpTransport
|
||||
|
||||
|
||||
async def test_oauth_uses_same_client_as_transport_streamable_http():
|
||||
transport = StreamableHttpTransport(
|
||||
"https://some.fake.url/",
|
||||
httpx_client_factory=lambda *args, **kwargs: httpx.AsyncClient(
|
||||
verify=False, *args, **kwargs
|
||||
),
|
||||
auth="oauth",
|
||||
)
|
||||
|
||||
async with transport.auth.httpx_client_factory() as httpx_client: # type: ignore[attr-defined]
|
||||
assert (
|
||||
httpx_client._transport._pool._ssl_context.verify_mode
|
||||
== VerifyMode.CERT_NONE
|
||||
)
|
||||
|
||||
|
||||
async def test_oauth_uses_same_client_as_transport_sse():
|
||||
transport = SSETransport(
|
||||
"https://some.fake.url/",
|
||||
httpx_client_factory=lambda *args, **kwargs: httpx.AsyncClient(
|
||||
verify=False, *args, **kwargs
|
||||
),
|
||||
auth="oauth",
|
||||
)
|
||||
|
||||
async with transport.auth.httpx_client_factory() as httpx_client: # type: ignore[attr-defined]
|
||||
assert (
|
||||
httpx_client._transport._pool._ssl_context.verify_mode
|
||||
== VerifyMode.CERT_NONE
|
||||
)
|
||||
|
|
@ -1,9 +1,12 @@
|
|||
import socket
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from fastmcp.utilities.tests import temporary_settings
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(items):
|
||||
"""Automatically mark tests in integration_tests folder with 'integration' marker."""
|
||||
|
|
@ -21,6 +24,20 @@ def import_rich_rule():
|
|||
yield
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def isolate_settings_home(tmp_path: Path):
|
||||
"""Ensure each test uses an isolated settings.home directory.
|
||||
|
||||
This prevents SQLite database locking issues on Windows when multiple
|
||||
tests share the same DiskStore directory in settings.home / "oauth-proxy".
|
||||
"""
|
||||
test_home = tmp_path / "fastmcp-test-home"
|
||||
test_home.mkdir(exist_ok=True)
|
||||
|
||||
with temporary_settings(home=test_home):
|
||||
yield
|
||||
|
||||
|
||||
def get_fn_name(fn: Callable[..., Any]) -> str:
|
||||
return fn.__name__ # ty: ignore[unresolved-attribute]
|
||||
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ class TestOpenAPIPerformance:
|
|||
# Generate multiple paths to create a reasonably sized schema
|
||||
for i in range(100):
|
||||
path = f"/test/{i}"
|
||||
schema["paths"][path] = {
|
||||
schema["paths"][path] = { # type: ignore[index]
|
||||
"get": {
|
||||
"operationId": f"test_{i}",
|
||||
"parameters": [
|
||||
|
|
|
|||
|
|
@ -284,7 +284,7 @@ async def test_github_oauth_authorization_redirect(github_server: str):
|
|||
|
||||
# Step 4: Approve consent
|
||||
approve_response = await http_client.post(
|
||||
f"{base_url}/consent/submit",
|
||||
f"{base_url}/consent",
|
||||
data={
|
||||
"action": "approve",
|
||||
"txn_id": txn_id,
|
||||
|
|
|
|||
|
|
@ -113,6 +113,43 @@ class TestSupabaseProvider:
|
|||
== "https://abc123.supabase.co/auth/v1"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"algorithm",
|
||||
["HS256", "RS256", "ES256"],
|
||||
)
|
||||
def test_algorithm_configuration(self, algorithm):
|
||||
"""Test that algorithm can be configured for different JWT signing methods."""
|
||||
provider = SupabaseProvider(
|
||||
project_url="https://abc123.supabase.co",
|
||||
base_url="https://myserver.com",
|
||||
algorithm=algorithm,
|
||||
)
|
||||
|
||||
assert provider.token_verifier.algorithm == algorithm # type: ignore[attr-defined]
|
||||
|
||||
def test_algorithm_default_es256(self):
|
||||
"""Test that algorithm defaults to ES256 when not specified."""
|
||||
provider = SupabaseProvider(
|
||||
project_url="https://abc123.supabase.co",
|
||||
base_url="https://myserver.com",
|
||||
)
|
||||
|
||||
assert provider.token_verifier.algorithm == "ES256" # type: ignore[attr-defined]
|
||||
|
||||
def test_algorithm_from_env_var(self):
|
||||
"""Test that algorithm can be configured via environment variable."""
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"FASTMCP_SERVER_AUTH_SUPABASE_PROJECT_URL": "https://env123.supabase.co",
|
||||
"FASTMCP_SERVER_AUTH_SUPABASE_BASE_URL": "https://envserver.com",
|
||||
"FASTMCP_SERVER_AUTH_SUPABASE_ALGORITHM": "RS256",
|
||||
},
|
||||
):
|
||||
provider = SupabaseProvider()
|
||||
|
||||
assert provider.token_verifier.algorithm == "RS256" # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def run_mcp_server(host: str, port: int) -> None:
|
||||
mcp = FastMCP(
|
||||
|
|
|
|||
|
|
@ -238,7 +238,11 @@ class TestServerSideStorage:
|
|||
test_client.cookies.set(k, v)
|
||||
approval_response = test_client.post(
|
||||
"/consent",
|
||||
data={"action": "approve", "txn": txn_id, "csrf_token": csrf_token},
|
||||
data={
|
||||
"action": "approve",
|
||||
"txn_id": txn_id,
|
||||
"csrf_token": csrf_token if csrf_token else "",
|
||||
},
|
||||
follow_redirects=False,
|
||||
)
|
||||
|
||||
|
|
@ -408,7 +412,7 @@ class TestCSRFProtection:
|
|||
with TestClient(app) as test_client:
|
||||
# Try to submit consent WITHOUT CSRF token
|
||||
response = test_client.post(
|
||||
"/consent/submit",
|
||||
"/consent",
|
||||
data={"action": "approve", "txn_id": txn_id},
|
||||
# No CSRF token!
|
||||
follow_redirects=False,
|
||||
|
|
@ -567,7 +571,7 @@ class TestConsentSecurity:
|
|||
for k, v in consent.cookies.items():
|
||||
c.cookies.set(k, v)
|
||||
r = c.post(
|
||||
"/consent/submit",
|
||||
"/consent",
|
||||
data={"action": "deny", "txn_id": txn_id, "csrf_token": csrf},
|
||||
follow_redirects=False,
|
||||
)
|
||||
|
|
@ -598,7 +602,7 @@ class TestConsentSecurity:
|
|||
for k, v in consent.cookies.items():
|
||||
c.cookies.set(k, v)
|
||||
r = c.post(
|
||||
"/consent/submit",
|
||||
"/consent",
|
||||
data={"action": "approve", "txn_id": txn_id, "csrf_token": csrf},
|
||||
follow_redirects=False,
|
||||
)
|
||||
|
|
@ -640,8 +644,12 @@ class TestConsentSecurity:
|
|||
for k, v in consent.cookies.items():
|
||||
c.cookies.set(k, v)
|
||||
r = c.post(
|
||||
"/consent/submit",
|
||||
data={"action": "approve", "txn_id": txn_id, "csrf_token": csrf},
|
||||
"/consent",
|
||||
data={
|
||||
"action": "approve",
|
||||
"txn_id": txn_id,
|
||||
"csrf_token": csrf if csrf else "",
|
||||
},
|
||||
follow_redirects=False,
|
||||
)
|
||||
# Extract approved cookie value
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from starlette.routing import Mount
|
|||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth import RemoteAuthProvider
|
||||
from fastmcp.server.auth.oauth_proxy import OAuthProxy
|
||||
from fastmcp.server.auth.providers.jwt import StaticTokenVerifier
|
||||
|
||||
|
||||
|
|
@ -194,3 +195,73 @@ class TestOAuthMounting:
|
|||
|
||||
data = response.json()
|
||||
assert data["resource"] == "https://api.example.com/outer/inner/mcp"
|
||||
|
||||
async def test_oauth_authorization_server_metadata_with_base_url_and_issuer_url(
|
||||
self, test_tokens
|
||||
):
|
||||
"""Test OAuth authorization server metadata when base_url and issuer_url differ.
|
||||
|
||||
This validates the fix for issue #2287 where operational OAuth endpoints
|
||||
(/authorize, /token) should be declared at base_url in the metadata,
|
||||
not at issuer_url.
|
||||
|
||||
Scenario: FastMCP server mounted at /api prefix
|
||||
- issuer_url: https://api.example.com (root level)
|
||||
- base_url: https://api.example.com/api (includes mount prefix)
|
||||
- Expected: metadata declares endpoints at base_url
|
||||
"""
|
||||
# Create OAuth proxy with different base_url and issuer_url
|
||||
token_verifier = StaticTokenVerifier(tokens=test_tokens)
|
||||
auth_provider = OAuthProxy(
|
||||
upstream_authorization_endpoint="https://upstream.example.com/authorize",
|
||||
upstream_token_endpoint="https://upstream.example.com/token",
|
||||
upstream_client_id="test-client-id",
|
||||
upstream_client_secret="test-client-secret",
|
||||
token_verifier=token_verifier,
|
||||
base_url="https://api.example.com/api", # Includes mount prefix
|
||||
issuer_url="https://api.example.com", # Root level
|
||||
)
|
||||
|
||||
mcp = FastMCP("test-server", auth=auth_provider)
|
||||
mcp_app = mcp.http_app(path="/mcp")
|
||||
|
||||
# Get well-known routes for mounting at root
|
||||
well_known_routes = auth_provider.get_well_known_routes(mcp_path="/mcp")
|
||||
|
||||
# Mount the app under /api prefix
|
||||
parent_app = Starlette(
|
||||
routes=[
|
||||
*well_known_routes, # Well-known routes at root level
|
||||
Mount("/api", app=mcp_app), # MCP app under /api
|
||||
],
|
||||
lifespan=mcp_app.lifespan,
|
||||
)
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
transport=httpx.ASGITransport(app=parent_app),
|
||||
base_url="https://api.example.com",
|
||||
) as client:
|
||||
# Fetch the authorization server metadata
|
||||
response = await client.get("/.well-known/oauth-authorization-server")
|
||||
assert response.status_code == 200
|
||||
|
||||
metadata = response.json()
|
||||
|
||||
# CRITICAL: The metadata should declare endpoints at base_url,
|
||||
# not issuer_url, because that's where they're actually mounted
|
||||
assert (
|
||||
metadata["authorization_endpoint"]
|
||||
== "https://api.example.com/api/authorize"
|
||||
)
|
||||
assert metadata["token_endpoint"] == "https://api.example.com/api/token"
|
||||
assert (
|
||||
metadata["registration_endpoint"]
|
||||
== "https://api.example.com/api/register"
|
||||
)
|
||||
|
||||
# The issuer field should use base_url (where the server is actually running)
|
||||
# Note: MCP SDK may or may not add a trailing slash
|
||||
assert metadata["issuer"] in [
|
||||
"https://api.example.com/api",
|
||||
"https://api.example.com/api/",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ from fastmcp.client.transports import FastMCPTransport
|
|||
from fastmcp.prompts.prompt import FunctionPrompt, Prompt
|
||||
from fastmcp.resources.resource import Resource
|
||||
from fastmcp.server.middleware.caching import (
|
||||
CachableToolResult,
|
||||
CallToolSettings,
|
||||
ResponseCachingMiddleware,
|
||||
ResponseCachingStatistics,
|
||||
|
|
@ -505,3 +506,18 @@ class TestResponseCachingMiddlewareIntegration:
|
|||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class TestCachableToolResult:
|
||||
def test_wrap_and_unwrap(self):
|
||||
tool_result = ToolResult(
|
||||
"unstructured content",
|
||||
structured_content={"structured": "content"},
|
||||
meta={"meta": "data"},
|
||||
)
|
||||
|
||||
cached_tool_result = CachableToolResult.wrap(tool_result).unwrap()
|
||||
|
||||
assert cached_tool_result.content == tool_result.content
|
||||
assert cached_tool_result.structured_content == tool_result.structured_content
|
||||
assert cached_tool_result.meta == tool_result.meta
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ async def proxy_server(fastmcp_server: FastMCP):
|
|||
"""
|
||||
A proxy server that forwards interactions with the proxy client to the given fastmcp server.
|
||||
"""
|
||||
return FastMCP.as_proxy(ProxyClient(fastmcp_server))
|
||||
return FastMCP.as_proxy(ProxyClient(fastmcp_server)) # type: ignore
|
||||
|
||||
|
||||
class TestProxyClient:
|
||||
|
|
@ -367,7 +367,7 @@ class TestProxyClient:
|
|||
else:
|
||||
return f"Elicitation {result.action}"
|
||||
|
||||
proxy_server = FastMCP.as_proxy(ProxyClient(fastmcp_server))
|
||||
proxy_server = FastMCP.as_proxy(ProxyClient(fastmcp_server)) # type: ignore
|
||||
|
||||
# Test that elicitation works correctly through the proxy
|
||||
async def elicitation_handler(
|
||||
|
|
|
|||
|
|
@ -124,3 +124,59 @@ class TestContextState:
|
|||
|
||||
assert context1.get_state("key1") == "key1-context1"
|
||||
assert context1.get_state("key-context3-only") is None
|
||||
|
||||
|
||||
class TestContextMeta:
|
||||
"""Test suite for Context meta functionality."""
|
||||
|
||||
def test_request_context_meta_access(self, context):
|
||||
"""Test that meta can be accessed from request context."""
|
||||
from mcp.server.lowlevel.server import request_ctx
|
||||
from mcp.shared.context import RequestContext
|
||||
|
||||
# Create a mock meta object with attributes
|
||||
class MockMeta:
|
||||
def __init__(self):
|
||||
self.user_id = "user-123"
|
||||
self.trace_id = "trace-456"
|
||||
self.custom_field = "custom-value"
|
||||
|
||||
mock_meta = MockMeta()
|
||||
|
||||
token = request_ctx.set(
|
||||
RequestContext( # type: ignore[arg-type]
|
||||
request_id=0,
|
||||
meta=mock_meta, # type: ignore[arg-type]
|
||||
session=MagicMock(wraps={}),
|
||||
lifespan_context=MagicMock(),
|
||||
)
|
||||
)
|
||||
|
||||
# Access meta through context
|
||||
retrieved_meta = context.request_context.meta
|
||||
assert retrieved_meta is not None
|
||||
assert retrieved_meta.user_id == "user-123"
|
||||
assert retrieved_meta.trace_id == "trace-456"
|
||||
assert retrieved_meta.custom_field == "custom-value"
|
||||
|
||||
request_ctx.reset(token)
|
||||
|
||||
def test_request_context_meta_none(self, context):
|
||||
"""Test that context handles None meta gracefully."""
|
||||
from mcp.server.lowlevel.server import request_ctx
|
||||
from mcp.shared.context import RequestContext
|
||||
|
||||
token = request_ctx.set(
|
||||
RequestContext( # type: ignore[arg-type]
|
||||
request_id=0,
|
||||
meta=None,
|
||||
session=MagicMock(wraps={}),
|
||||
lifespan_context=MagicMock(),
|
||||
)
|
||||
)
|
||||
|
||||
# Access meta through context
|
||||
retrieved_meta = context.request_context.meta
|
||||
assert retrieved_meta is None
|
||||
|
||||
request_ctx.reset(token)
|
||||
|
|
|
|||
|
|
@ -1179,6 +1179,7 @@ class TestToolOutputSchema:
|
|||
"_meta": None,
|
||||
},
|
||||
],
|
||||
meta=None,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -284,7 +284,7 @@ async def test_multi_client_parallel_calls(tmp_path: Path):
|
|||
exceptions = [result for result in results if isinstance(result, Exception)]
|
||||
assert len(exceptions) == 0
|
||||
assert len(results) == 40
|
||||
assert all(len(result) == 2 for result in results)
|
||||
assert all(len(result) == 2 for result in results) # type: ignore[arg-type]
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
|
|
@ -636,6 +636,7 @@ async def test_canonical_multi_client_with_transforms(tmp_path: Path):
|
|||
assert "test_1_transformed_add" not in tools_by_name
|
||||
|
||||
|
||||
@pytest.mark.flaky(retries=3)
|
||||
async def test_multi_client_transform_with_filtering(tmp_path: Path):
|
||||
"""
|
||||
Tests that tag-based filtering works when using a transforming MCPConfig.
|
||||
|
|
|
|||
|
|
@ -1405,6 +1405,70 @@ class TestAutomaticStructuredContent:
|
|||
assert result.data.verified is True
|
||||
|
||||
|
||||
class TestToolResultCasting:
|
||||
@pytest.fixture
|
||||
async def client(self):
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.client import Client
|
||||
|
||||
mcp = FastMCP()
|
||||
|
||||
@mcp.tool
|
||||
def test_tool(
|
||||
unstructured: str | None = None,
|
||||
structured: dict[str, Any] | None = None,
|
||||
meta: dict[str, Any] | None = None,
|
||||
):
|
||||
return ToolResult(
|
||||
content=unstructured,
|
||||
structured_content=structured,
|
||||
meta=meta,
|
||||
)
|
||||
|
||||
async with Client(mcp) as client:
|
||||
yield client
|
||||
|
||||
async def test_only_unstructured_content(self, client):
|
||||
result = await client.call_tool("test_tool", {"unstructured": "test data"})
|
||||
|
||||
assert result.content[0].type == "text"
|
||||
assert result.content[0].text == "test data"
|
||||
assert result.structured_content is None
|
||||
assert result.meta is None
|
||||
|
||||
async def test_neither_unstructured_or_structured_content(self, client):
|
||||
from fastmcp.exceptions import ToolError
|
||||
|
||||
with pytest.raises(ToolError):
|
||||
await client.call_tool("test_tool", {})
|
||||
|
||||
async def test_structured_and_unstructured_content(self, client):
|
||||
result = await client.call_tool(
|
||||
"test_tool",
|
||||
{"unstructured": "test data", "structured": {"data_type": "test"}},
|
||||
)
|
||||
|
||||
assert result.content[0].type == "text"
|
||||
assert result.content[0].text == "test data"
|
||||
assert result.structured_content == {"data_type": "test"}
|
||||
assert result.meta is None
|
||||
|
||||
async def test_structured_unstructured_and_meta_content(self, client):
|
||||
result = await client.call_tool(
|
||||
"test_tool",
|
||||
{
|
||||
"unstructured": "test data",
|
||||
"structured": {"data_type": "test"},
|
||||
"meta": {"some": "metadata"},
|
||||
},
|
||||
)
|
||||
|
||||
assert result.content[0].type == "text"
|
||||
assert result.content[0].text == "test data"
|
||||
assert result.structured_content == {"data_type": "test"}
|
||||
assert result.meta == {"some": "metadata"}
|
||||
|
||||
|
||||
class TestUnionReturnTypes:
|
||||
"""Tests for tools with union return types."""
|
||||
|
||||
|
|
|
|||
|
|
@ -100,13 +100,14 @@ def fastapi_app() -> FastAPI:
|
|||
),
|
||||
):
|
||||
"""Get details about a specific item."""
|
||||
price = float(item_id) * 10.0
|
||||
item = {
|
||||
"id": item_id,
|
||||
"name": f"Item {item_id}",
|
||||
"price": float(item_id) * 10.0,
|
||||
"price": price,
|
||||
}
|
||||
if include_tax:
|
||||
item["tax"] = item["price"] * 0.2
|
||||
item["tax"] = price * 0.2
|
||||
return item
|
||||
|
||||
@app.put(
|
||||
|
|
|
|||
|
|
@ -1225,9 +1225,9 @@ class TestNameHandling:
|
|||
Type = json_schema_to_type(schema)
|
||||
assert Type.__name__ == "Parent"
|
||||
child_field_type = get_dataclass_field(Type, "child").type
|
||||
assert child_field_type.__origin__ is Union # ty: ignore[possibly-unbound-attribute]
|
||||
assert child_field_type.__args__[0].__name__ == "Child" # ty: ignore[possibly-unbound-attribute]
|
||||
assert child_field_type.__args__[1] is type(None) # ty: ignore[possibly-unbound-attribute]
|
||||
assert child_field_type.__origin__ is Union # ty: ignore[possibly-missing-attribute]
|
||||
assert child_field_type.__args__[0].__name__ == "Child" # ty: ignore[possibly-missing-attribute]
|
||||
assert child_field_type.__args__[1] is type(None) # ty: ignore[possibly-missing-attribute]
|
||||
|
||||
def test_recursive_schema_naming(self):
|
||||
schema = {
|
||||
|
|
@ -1240,9 +1240,9 @@ class TestNameHandling:
|
|||
|
||||
next_field_type = get_dataclass_field(Type, "next").type
|
||||
|
||||
assert next_field_type.__origin__ is Union # ty: ignore[possibly-unbound-attribute]
|
||||
assert next_field_type.__args__[0].__forward_arg__ == "Node" # ty: ignore[possibly-unbound-attribute]
|
||||
assert next_field_type.__args__[1] is type(None) # ty: ignore[possibly-unbound-attribute]
|
||||
assert next_field_type.__origin__ is Union # ty: ignore[possibly-missing-attribute]
|
||||
assert next_field_type.__args__[0].__forward_arg__ == "Node" # ty: ignore[possibly-missing-attribute]
|
||||
assert next_field_type.__args__[1] is type(None) # ty: ignore[possibly-missing-attribute]
|
||||
|
||||
def test_name_caching_with_different_titles(self):
|
||||
"""Ensure schemas with different titles create different cached classes"""
|
||||
|
|
|
|||
182
uv.lock
generated
182
uv.lock
generated
|
|
@ -50,14 +50,14 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "authlib"
|
||||
version = "1.6.1"
|
||||
version = "1.6.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cryptography" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/a1/d8d1c6f8bc922c0b87ae0d933a8ed57be1bef6970894ed79c2852a153cd3/authlib-1.6.1.tar.gz", hash = "sha256:4dffdbb1460ba6ec8c17981a4c67af7d8af131231b5a36a88a1e8c80c111cdfd", size = 159988, upload-time = "2025-07-20T07:38:42.834Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/3f/1d3bbd0bf23bdd99276d4def22f29c27a914067b4cf66f753ff9b8bbd0f3/authlib-1.6.5.tar.gz", hash = "sha256:6aaf9c79b7cc96c900f0b284061691c5d4e61221640a948fe690b556a6d6d10b", size = 164553, upload-time = "2025-10-02T13:36:09.489Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/58/cc6a08053f822f98f334d38a27687b69c6655fb05cd74a7a5e70a2aeed95/authlib-1.6.1-py2.py3-none-any.whl", hash = "sha256:e9d2031c34c6309373ab845afc24168fe9e93dc52d252631f52642f21f5ed06e", size = 239299, upload-time = "2025-07-20T07:38:39.259Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/aa/5082412d1ee302e9e7d80b6949bc4d2a8fa1149aaab610c5fc24709605d6/authlib-1.6.5-py2.py3-none-any.whl", hash = "sha256:3e0e0507807f842b02175507bdee8957a1d5707fd4afb17c32fb43fee90b6e3a", size = 243608, upload-time = "2025-10-02T13:36:07.637Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -162,15 +162,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfgv"
|
||||
version = "3.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.4"
|
||||
|
|
@ -420,18 +411,19 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "cyclopts"
|
||||
version = "3.22.5"
|
||||
version = "4.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
{ name = "docstring-parser", marker = "python_full_version < '4'" },
|
||||
{ name = "docstring-parser" },
|
||||
{ name = "rich" },
|
||||
{ name = "rich-rst" },
|
||||
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/d5/24c6c894f3833bc93d4944c2064309dfd633c0becf93e16fc79d76edd388/cyclopts-3.22.5.tar.gz", hash = "sha256:fa2450b9840abc41c6aa37af5eaeafc7a1264e08054e3a2fe39d49aa154f592a", size = 74890, upload-time = "2025-07-31T18:18:37.336Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8a/51/a67b17fac2530d22216a335bd10f48631412dd824013ea559ec236668f76/cyclopts-4.2.1.tar.gz", hash = "sha256:49bb4c35644e7a9658f706ade4cf1a9958834b2dca4425e2fafecf8a0537fac7", size = 148693, upload-time = "2025-10-31T14:30:58.681Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/df/e5/a7b6db64f08cfe065e531ec6b508fa7dac704fab70d05adb5bc0c2c1d1b6/cyclopts-3.22.5-py3-none-any.whl", hash = "sha256:92efb4a094d9812718d7efe0bffa319a19cb661f230dbf24406c18cd8809fb82", size = 84994, upload-time = "2025-07-31T18:18:35.939Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/1d/2b313e157c9c7bba319e42f464d15073d32a81ac4827bdc5b7de38832b3e/cyclopts-4.2.1-py3-none-any.whl", hash = "sha256:17a801faa814988b0307385ef8aaeb6b14b4d64473015a2d66bde9ea13f14d9c", size = 184333, upload-time = "2025-10-31T14:30:57.581Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -461,15 +453,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "distlib"
|
||||
version = "0.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "distro"
|
||||
version = "1.9.0"
|
||||
|
|
@ -499,11 +482,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "docutils"
|
||||
version = "0.22"
|
||||
version = "0.22.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e9/86/5b41c32ecedcfdb4c77b28b6cb14234f252075f8cdb254531727a35547dd/docutils-0.22.tar.gz", hash = "sha256:ba9d57750e92331ebe7c08a1bbf7a7f8143b86c476acd51528b042216a6aad0f", size = 2277984, upload-time = "2025-07-29T15:20:31.06Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4a/c0/89fe6215b443b919cb98a5002e107cb5026854ed1ccb6b5833e0768419d1/docutils-0.22.2.tar.gz", hash = "sha256:9fdb771707c8784c8f2728b67cb2c691305933d68137ef95a75db5f4dfbc213d", size = 2289092, upload-time = "2025-09-20T17:55:47.994Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/44/57/8db39bc5f98f042e0153b1de9fb88e1a409a33cda4dd7f723c2ed71e01f6/docutils-0.22-py3-none-any.whl", hash = "sha256:4ed966a0e96a0477d852f7af31bdcb3adc049fbb35ccba358c2ea8a03287615e", size = 630709, upload-time = "2025-07-29T15:20:28.335Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/dd/f95350e853a4468ec37478414fc04ae2d61dad7a947b3015c3dcc51a09b9/docutils-0.22.2-py3-none-any.whl", hash = "sha256:b0e98d679283fc3bb0ead8a5da7f501baa632654e7056e9c5846842213d674d8", size = 632667, upload-time = "2025-09-20T17:55:43.052Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -611,7 +594,7 @@ dev = [
|
|||
{ name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "ipython", version = "9.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
{ name = "pdbpp" },
|
||||
{ name = "pre-commit" },
|
||||
{ name = "prek" },
|
||||
{ name = "psutil" },
|
||||
{ name = "pyinstrument" },
|
||||
{ name = "pyperclip" },
|
||||
|
|
@ -631,12 +614,12 @@ dev = [
|
|||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "authlib", specifier = ">=1.5.2" },
|
||||
{ name = "cyclopts", specifier = ">=3.0.0" },
|
||||
{ name = "authlib", specifier = ">=1.6.5" },
|
||||
{ name = "cyclopts", specifier = ">=4.0.0" },
|
||||
{ name = "exceptiongroup", specifier = ">=1.2.2" },
|
||||
{ name = "httpx", specifier = ">=0.28.1" },
|
||||
{ name = "jsonschema-path", specifier = ">=0.3.4" },
|
||||
{ name = "mcp", specifier = ">=1.17.0,<2.0.0" },
|
||||
{ name = "mcp", specifier = ">=1.19.0,<2.0.0" },
|
||||
{ name = "openai", marker = "extra == 'openai'", specifier = ">=1.102.0" },
|
||||
{ name = "openapi-pydantic", specifier = ">=0.5.1" },
|
||||
{ name = "platformdirs", specifier = ">=4.0.0" },
|
||||
|
|
@ -657,32 +640,23 @@ dev = [
|
|||
{ name = "fastmcp", extras = ["openai"] },
|
||||
{ name = "inline-snapshot", extras = ["dirty-equals"], specifier = ">=0.27.2" },
|
||||
{ name = "ipython", specifier = ">=8.12.3" },
|
||||
{ name = "pdbpp", specifier = ">=0.10.3" },
|
||||
{ name = "pre-commit" },
|
||||
{ name = "psutil" },
|
||||
{ name = "pdbpp", specifier = ">=0.11.7" },
|
||||
{ name = "prek", specifier = ">=0.2.12" },
|
||||
{ name = "psutil", specifier = ">=7.0.0" },
|
||||
{ name = "pyinstrument", specifier = ">=5.0.2" },
|
||||
{ name = "pyperclip", specifier = ">=1.9.0" },
|
||||
{ name = "pytest", specifier = ">=8.3.3" },
|
||||
{ name = "pytest-asyncio", specifier = ">=1.2.0" },
|
||||
{ name = "pytest-cov", specifier = ">=6.1.1" },
|
||||
{ name = "pytest-env", specifier = ">=1.1.5" },
|
||||
{ name = "pytest-flakefinder" },
|
||||
{ name = "pytest-flakefinder", specifier = ">=1.1.0" },
|
||||
{ name = "pytest-httpx", specifier = ">=0.35.0" },
|
||||
{ name = "pytest-report", specifier = ">=0.2.1" },
|
||||
{ name = "pytest-retry", specifier = ">=1.7.0" },
|
||||
{ name = "pytest-timeout", specifier = ">=2.4.0" },
|
||||
{ name = "pytest-xdist", specifier = ">=3.6.1" },
|
||||
{ name = "ruff" },
|
||||
{ name = "ty", specifier = ">=0.0.1a19" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.18.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" },
|
||||
{ name = "ruff", specifier = ">=0.12.8" },
|
||||
{ name = "ty", specifier = "==0.0.1a25" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -731,15 +705,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819, upload-time = "2023-12-22T08:01:19.89Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "identify"
|
||||
version = "2.6.13"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/82/ca/ffbabe3635bb839aa36b3a893c91a9b0d368cb4d8073e03a12896970af82/identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32", size = 99243, upload-time = "2025-08-09T19:35:00.6Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/ce/461b60a3ee109518c055953729bf9ed089a04db895d47e95444071dcdef2/identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b", size = 99153, upload-time = "2025-08-09T19:34:59.1Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
|
|
@ -1068,7 +1033,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "mcp"
|
||||
version = "1.18.0"
|
||||
version = "1.19.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
|
|
@ -1083,9 +1048,9 @@ dependencies = [
|
|||
{ name = "starlette" },
|
||||
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1a/e0/fe34ce16ea2bacce489ab859abd1b47ae28b438c3ef60b9c5eee6c02592f/mcp-1.18.0.tar.gz", hash = "sha256:aa278c44b1efc0a297f53b68df865b988e52dd08182d702019edcf33a8e109f6", size = 482926, upload-time = "2025-10-16T19:19:55.125Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/69/2b/916852a5668f45d8787378461eaa1244876d77575ffef024483c94c0649c/mcp-1.19.0.tar.gz", hash = "sha256:213de0d3cd63f71bc08ffe9cc8d4409cc87acffd383f6195d2ce0457c021b5c1", size = 444163, upload-time = "2025-10-24T01:11:15.839Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/44/f5970e3e899803823826283a70b6003afd46f28e082544407e24575eccd3/mcp-1.18.0-py3-none-any.whl", hash = "sha256:42f10c270de18e7892fdf9da259029120b1ea23964ff688248c69db9d72b1d0a", size = 168762, upload-time = "2025-10-16T19:19:53.2Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/a3/3e71a875a08b6a830b88c40bc413bff01f1650f1efe8a054b5e90a9d4f56/mcp-1.19.0-py3-none-any.whl", hash = "sha256:f5907fe1c0167255f916718f376d05f09a830a215327a3ccdd5ec8a519f2e572", size = 170105, upload-time = "2025-10-24T01:11:14.151Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1106,15 +1071,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/2b/9f/7ba6f94fc1e9ac3d2b853fdff3035fb2fa5afbed898c4a72b8a020610594/more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e", size = 65278, upload-time = "2025-04-22T14:17:40.49Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodeenv"
|
||||
version = "1.9.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openai"
|
||||
version = "2.6.1"
|
||||
|
|
@ -1226,19 +1182,29 @@ wheels = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit"
|
||||
version = "4.3.0"
|
||||
name = "prek"
|
||||
version = "0.2.12"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cfgv" },
|
||||
{ name = "identify" },
|
||||
{ name = "nodeenv" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "virtualenv" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f4/65/648c9e3f3e20eaeb3b2f69d6f06d3227ebb54fc667598855f0bc138def53/prek-0.2.12.tar.gz", hash = "sha256:751587889cf0d8e9f98f743f31da6ca9f38966df186a5efea3796be0fc9d7e97", size = 320905, upload-time = "2025-10-27T12:23:02.505Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/b7/732c7fcd2189806c8a161dc68bcf63d0abe72f32b912760b525f0b680996/prek-0.2.12-py3-none-linux_armv6l.whl", hash = "sha256:90f5e99cb7a82e55589cd64aa9c4893c84f975b6f26df463a89a31179effbf3d", size = 4440642, upload-time = "2025-10-27T12:22:28.056Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/ad/8b22926aebd4da431c09f31355f300803290699d0df542d30524af8618a2/prek-0.2.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9cb3779e62d2b34f25dc22550d949e7fed7b671d91fa0471cb3bca42530b7172", size = 4557630, upload-time = "2025-10-27T12:22:30.105Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/8c/a1e52eb8cc519151e7548e606a4fe52975e348d117d7594bbe89a6c9b77c/prek-0.2.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8a0b4590272eb599f3be350fabfa8fb7067569d24852f65239826dcca3b37b41", size = 4251450, upload-time = "2025-10-27T12:22:32.012Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/47/5747c4ef0567192fa38123ef936dd263f9d470d012fa3d1af7259ea3631c/prek-0.2.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:141cdeefc926dfebdb429b055c35f397071a40db66db73efedb26f19e2148e5b", size = 4430341, upload-time = "2025-10-27T12:22:33.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/42/090acc2c265938f2c8b255a710f57b8b7159165d5af7c144defda7ef98af/prek-0.2.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9568f80a7ad2ec267e4ad650b8e734cbb5aac7db0984359a8f6da1230a65c36", size = 4379066, upload-time = "2025-10-27T12:22:35.955Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/e5/fe684fd0fa5e466a5116d79aeaa36a8cb31bc8da323cb624dea40a7cda00/prek-0.2.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4760c423fbf182f166892db157bc8ab6a3af7aff8ecd62ec151e9dcb07bc37ed", size = 4670900, upload-time = "2025-10-27T12:22:37.64Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/9c/8d1785ffd8cfa401639b9f27a8d27e66480b5f8969e5236aa46ed2e45142/prek-0.2.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8c095432d47df7704fd4b73e4a0a7e28bd33528b80724635c383db052c4c23af", size = 5118603, upload-time = "2025-10-27T12:22:39.875Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/f5/25e250d41848dc5c882c8ed4265ca64afa8763d0d8b959cb1a70097450ec/prek-0.2.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6e0f654551cce0d2a7f14cf69e2498a89e125c3acb4b25cb1f6a10238a0b9834", size = 5032115, upload-time = "2025-10-27T12:22:41.629Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/18/966c1293561e8cd789eebccdbb38d24d20da17a648967ca995491f742a08/prek-0.2.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3d7923825607271f1a23c1378d9345c8e8ac487a55d3199efe1f7069c018ed2", size = 5168683, upload-time = "2025-10-27T12:22:43.641Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/d7/593d7d9e96a11920b426014fc5aa1dc6b25a12b1b98b68629fe727fcd1b2/prek-0.2.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b539e97ecb02322804688e5dfbee149907b54c5aa85a5cf822e2584eddb660", size = 4734763, upload-time = "2025-10-27T12:22:45.582Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/07/3c27229249cc0968a59448359fdde0a93ea44f12cee99155665a4d03dc60/prek-0.2.12-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:33e2761710c89d16f0a1777607f22d07b49355198af591c0f1b853b76865ad3c", size = 4444866, upload-time = "2025-10-27T12:22:47.559Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/17/dc5255ac64b841d4cfbf8e4fb8e8f2cd2a340b1419a280a6a1cce3b609a5/prek-0.2.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:848df65ea8aeb75326b8f7d9a7bbf286d5e84e39fcb36bc195c512b701c32b5b", size = 4551787, upload-time = "2025-10-27T12:22:49.324Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/e8/c21f4576f9bf0d16462dee863b4abb1030f302b562c8f404aa02a65fc9af/prek-0.2.12-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:eeb90e112f99665a7d1f33d337be79141cb42c6d18dc51260001345165d25ef5", size = 4360926, upload-time = "2025-10-27T12:22:50.918Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/07/b6d54082e5b10f94ec8e4d2b0a5b1555b2d28a124ee90d6bdb485ce8533c/prek-0.2.12-py3-none-musllinux_1_1_i686.whl", hash = "sha256:38eb44fb02e4cde98dd75e1eaba48860bd251cc12014452b2c55c65e2ff3911a", size = 4568253, upload-time = "2025-10-27T12:22:52.548Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/5a/71a037daa9d582ac3c5f0fab39c6e2dee6c244a10c56909a89871208204e/prek-0.2.12-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:82733a882efdd064da1baa38cb433fe86bd5e858be9248501a6e86e938dbf2e3", size = 4837022, upload-time = "2025-10-27T12:22:54.122Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/39/5f9cbfe0190e4f0618b4d76c48744d1a75fb18ae8376d1f1f42f0e0bbd66/prek-0.2.12-py3-none-win32.whl", hash = "sha256:491b50f1686ade8472edc67fc6cd088fbb7cee416ee8d974649fc4f7f9357660", size = 4274195, upload-time = "2025-10-27T12:22:57.25Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/c4/0e4c03c96ee3530053aa77b37361cc9d0e23e15a90017b81838a45cf54a8/prek-0.2.12-py3-none-win_amd64.whl", hash = "sha256:aecb61e06636c90dcfd14dd02901474a6d77dbafd930a0ab136d256800d8f8e1", size = 4845173, upload-time = "2025-10-27T12:22:58.916Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/c3/6b40262600bf7daeaa65a9ded17244c2f4b0f6d032119495ec28326a6978/prek-0.2.12-py3-none-win_arm64.whl", hash = "sha256:8f203c05afa4e7126d86d16ad1916b9676cbed127e95dcb230a06299bc5bcf6b", size = 4528164, upload-time = "2025-10-27T12:23:01.219Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1810,15 +1776,15 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "rich-rst"
|
||||
version = "1.3.1"
|
||||
version = "1.3.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "docutils" },
|
||||
{ name = "rich" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/69/5514c3a87b5f10f09a34bb011bc0927bc12c596c8dae5915604e71abc386/rich_rst-1.3.1.tar.gz", hash = "sha256:fad46e3ba42785ea8c1785e2ceaa56e0ffa32dbe5410dec432f37e4107c4f383", size = 13839, upload-time = "2024-04-30T04:40:38.125Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bc/6d/a506aaa4a9eaa945ed8ab2b7347859f53593864289853c5d6d62b77246e0/rich_rst-1.3.2.tar.gz", hash = "sha256:a1196fdddf1e364b02ec68a05e8ff8f6914fee10fbca2e6b6735f166bb0da8d4", size = 14936, upload-time = "2025-10-14T16:49:45.332Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/bc/cc4e3dbc5e7992398dcb7a8eda0cbcf4fb792a0cdb93f857b478bf3cf884/rich_rst-1.3.1-py3-none-any.whl", hash = "sha256:498a74e3896507ab04492d326e794c3ef76e7cda078703aa592d1853d91098c1", size = 11621, upload-time = "2024-04-30T04:40:32.619Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl", hash = "sha256:a99b4907cbe118cf9d18b0b44de272efa61f15117c61e39ebdc431baf5df722a", size = 12567, upload-time = "2025-10-14T16:49:42.953Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2104,27 +2070,27 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "ty"
|
||||
version = "0.0.1a20"
|
||||
version = "0.0.1a25"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7a/82/a5e3b4bc5280ec49c4b0b43d0ff727d58c7df128752c9c6f97ad0b5f575f/ty-0.0.1a20.tar.gz", hash = "sha256:933b65a152f277aa0e23ba9027e5df2c2cc09e18293e87f2a918658634db5f15", size = 4194773, upload-time = "2025-09-03T12:35:46.775Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f6/6b/e73bc3c1039ea72936158a08313155a49e5aa5e7db5205a149fe516a4660/ty-0.0.1a25.tar.gz", hash = "sha256:5550b24b9dd0e0f8b4b2c1f0fcc608a55d0421dd67b6c364bc7bf25762334511", size = 4403670, upload-time = "2025-10-29T19:40:23.647Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/45/c8/f7d39392043d5c04936f6cad90e50eb661965ed092ca4bfc01db917d7b8a/ty-0.0.1a20-py3-none-linux_armv6l.whl", hash = "sha256:f73a7aca1f0d38af4d6999b375eb00553f3bfcba102ae976756cc142e14f3450", size = 8443599, upload-time = "2025-09-03T12:35:04.289Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/57/5aec78f9b8a677b7439ccded7d66c3361e61247e0f6b14e659b00dd01008/ty-0.0.1a20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cad12c857ea4b97bf61e02f6796e13061ccca5e41f054cbd657862d80aa43bae", size = 8618102, upload-time = "2025-09-03T12:35:07.448Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/20/50c9107d93cdb55676473d9dc4e2339af6af606660c9428d3b86a1b2a476/ty-0.0.1a20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f153b65c7fcb6b8b59547ddb6353761b3e8d8bb6f0edd15e3e3ac14405949f7a", size = 8192167, upload-time = "2025-09-03T12:35:09.706Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/28/018b2f330109cee19e81c5ca9df3dc29f06c5778440eb9af05d4550c4302/ty-0.0.1a20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8c4336987a6a781d4392a9fd7b3a39edb7e4f3dd4f860e03f46c932b52aefa2", size = 8349256, upload-time = "2025-09-03T12:35:11.76Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/c9/2f8797a05587158f52b142278796ffd72c893bc5ad41840fce5aeb65c6f2/ty-0.0.1a20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3ff75cd4c744d09914e8c9db8d99e02f82c9379ad56b0a3fc4c5c9c923cfa84e", size = 8271214, upload-time = "2025-09-03T12:35:13.741Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/d4/2cac5e5eb9ee51941358cb3139aadadb59520cfaec94e4fcd2b166969748/ty-0.0.1a20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e26437772be7f7808868701f2bf9e14e706a6ec4c7d02dbd377ff94d7ba60c11", size = 9264939, upload-time = "2025-09-03T12:35:16.896Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/96/a6f2b54e484b2c6a5488f217882237dbdf10f0fdbdb6cd31333d57afe494/ty-0.0.1a20-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:83a7ee12465841619b5eb3ca962ffc7d576bb1c1ac812638681aee241acbfbbe", size = 9743137, upload-time = "2025-09-03T12:35:19.799Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/67/95b40dcbec3d222f3af5fe5dd1ce066d42f8a25a2f70d5724490457048e7/ty-0.0.1a20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:726d0738be4459ac7ffae312ba96c5f486d6cbc082723f322555d7cba9397871", size = 9368153, upload-time = "2025-09-03T12:35:22.569Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/24/689fa4c4270b9ef9a53dc2b1d6ffade259ba2c4127e451f0629e130ea46a/ty-0.0.1a20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b481f26513f38543df514189fb16744690bcba8d23afee95a01927d93b46e36", size = 9099637, upload-time = "2025-09-03T12:35:24.94Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/5b/913011cbf3ea4030097fb3c4ce751856114c9e1a5e1075561a4c5242af9b/ty-0.0.1a20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7abbe3c02218c12228b1d7c5f98c57240029cc3bcb15b6997b707c19be3908c1", size = 8952000, upload-time = "2025-09-03T12:35:27.288Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/f9/f5ba2ae455b20c5bb003f9940ef8142a8c4ed9e27de16e8f7472013609db/ty-0.0.1a20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fff51c75ee3f7cc6d7722f2f15789ef8ffe6fd2af70e7269ac785763c906688e", size = 8217938, upload-time = "2025-09-03T12:35:29.54Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/62/17002cf9032f0981cdb8c898d02422c095c30eefd69ca62a8b705d15bd0f/ty-0.0.1a20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b4124ab75e0e6f09fe7bc9df4a77ee43c5e0ef7e61b0c149d7c089d971437cbd", size = 8292369, upload-time = "2025-09-03T12:35:31.748Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/d6/0879b1fb66afe1d01d45c7658f3849aa641ac4ea10679404094f3b40053e/ty-0.0.1a20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8a138fa4f74e6ed34e9fd14652d132409700c7ff57682c2fed656109ebfba42f", size = 8811973, upload-time = "2025-09-03T12:35:33.997Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/1e/70bf0348cfe8ba5f7532983f53c508c293ddf5fa9f942ed79a3c4d576df3/ty-0.0.1a20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8eff8871d6b88d150e2a67beba2c57048f20c090c219f38ed02eebaada04c124", size = 9010990, upload-time = "2025-09-03T12:35:36.766Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/ca/03d85c7650359247b1ca3f38a0d869a608ef540450151920e7014ed58292/ty-0.0.1a20-py3-none-win32.whl", hash = "sha256:3c2ace3a22fab4bd79f84c74e3dab26e798bfba7006bea4008d6321c1bd6efc6", size = 8100746, upload-time = "2025-09-03T12:35:40.007Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/53/7a1937b8c7a66d0c8ed7493de49ed454a850396fe137d2ae12ed247e0b2f/ty-0.0.1a20-py3-none-win_amd64.whl", hash = "sha256:f41e77ff118da3385915e13c3f366b3a2f823461de54abd2e0ca72b170ba0f19", size = 8748861, upload-time = "2025-09-03T12:35:42.175Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/36/5a3a70c5d497d3332f9e63cabc9c6f13484783b832fecc393f4f1c0c4aa8/ty-0.0.1a20-py3-none-win_arm64.whl", hash = "sha256:d8ac1c5a14cda5fad1a8b53959d9a5d979fe16ce1cc2785ea8676fed143ac85f", size = 8269906, upload-time = "2025-09-03T12:35:45.045Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/3b/4457231238a2eeb04cba4ba7cc33d735be68ee46ca40a98ae30e187de864/ty-0.0.1a25-py3-none-linux_armv6l.whl", hash = "sha256:d35b2c1f94a014a22875d2745aa0432761d2a9a8eb7212630d5caf547daeef6d", size = 8878803, upload-time = "2025-10-29T19:39:42.243Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/fa/a328713dd310018fc7a381693d8588185baa2fdae913e01a6839187215df/ty-0.0.1a25-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:192edac94675a468bac7f6e04687a77a64698e4e1fe01f6a048bf9b6dde5b703", size = 8695667, upload-time = "2025-10-29T19:39:45.179Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/e8/5707939118992ced2bf5385adc3ede7723c1b717b07ad14c495eea1e47b4/ty-0.0.1a25-py3-none-macosx_11_0_arm64.whl", hash = "sha256:949523621f336e01bc7d687b7bd08fe838edadbdb6563c2c057ed1d264e820cf", size = 8159012, upload-time = "2025-10-29T19:39:47.011Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/fb/ff313aa71602225cd78f1bce3017713d6d1b1c1e0fa8101ead4594a60d95/ty-0.0.1a25-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94f78f621458c05e59e890061021198197f29a7b51a33eda82bbb036e7ed73d7", size = 8433675, upload-time = "2025-10-29T19:39:48.443Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/8d/cc7e7fb57215a15b575a43ed042bdd92971871e0decec1b26d2e7d969465/ty-0.0.1a25-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d9656fca8062a2c6709c30d76d662c96d2e7dbfee8f70e55ec6b6afd67b5d447", size = 8668456, upload-time = "2025-10-29T19:39:50.412Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/6d/d7bf5909ed2dcdcbc1e2ca7eea80929893e2d188d9c36b3fcb2b36532ff6/ty-0.0.1a25-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9f3bbf523b49935bbd76e230408d858dce0d614f44f5807bbbd0954f64e0f01", size = 9023543, upload-time = "2025-10-29T19:39:52.292Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/b8/72bcefb4be32e5a84f0b21de2552f16cdb4cae3eb271ac891c8199c26b1a/ty-0.0.1a25-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f13ea9815f4a54a0a303ca7bf411b0650e3c2a24fc6c7889ffba2c94f5e97a6a", size = 9700013, upload-time = "2025-10-29T19:39:57.283Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/0d/cf7e794b840cf6b0bbecb022e593c543f85abad27a582241cf2095048cb1/ty-0.0.1a25-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eab6e33ebe202a71a50c3d5a5580e3bc1a85cda3ffcdc48cec3f1c693b7a873b", size = 9372574, upload-time = "2025-10-29T19:40:04.532Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/71/2d35e7d51b48eabd330e2f7b7e0bce541cbd95950c4d2f780e85f3366af1/ty-0.0.1a25-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6b9a31da43424cdab483703a54a561b93aabba84630788505329fc5294a9c62", size = 9535726, upload-time = "2025-10-29T19:40:06.548Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/d3/01ecc23bbd8f3e0dfbcf9172d06d84e88155c5f416f1491137e8066fd859/ty-0.0.1a25-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a90d897a7c1a5ae9b41a4c7b0a42262a06361476ad88d783dbedd7913edadbc", size = 9003380, upload-time = "2025-10-29T19:40:08.683Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/f9/cde9380d8a1a6ca61baeb9aecb12cbec90d489aa929be55cd78ad5c2ccd9/ty-0.0.1a25-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:93c7e7ab2859af0f866d34d27f4ae70dd4fb95b847387f082de1197f9f34e068", size = 8401833, upload-time = "2025-10-29T19:40:10.627Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/39/0acf3625b0c495011795a391016b572f97a812aca1d67f7a76621fdb9ebf/ty-0.0.1a25-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4a247061bd32bae3865a236d7f8b6c9916c80995db30ae1600999010f90623a9", size = 8706761, upload-time = "2025-10-29T19:40:12.575Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/73/7de1648f3563dd9d416d36ab5f1649bfd7b47a179135027f31d44b89a246/ty-0.0.1a25-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1711dd587eccf04fd50c494dc39babe38f4cb345bc3901bf1d8149cac570e979", size = 8792426, upload-time = "2025-10-29T19:40:14.553Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/8a/b6e761a65eac7acd10b2e452f49b2d8ae0ea163ca36bb6b18b2dadae251b/ty-0.0.1a25-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5f4c9b0cf7995e2e3de9bab4d066063dea92019f2f62673b7574e3612643dd35", size = 9103991, upload-time = "2025-10-29T19:40:16.332Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/25/9324ae947fcc4322470326cf8276a3fc2f08dc82adec1de79d963fdf7af5/ty-0.0.1a25-py3-none-win32.whl", hash = "sha256:168fc8aee396d617451acc44cd28baffa47359777342836060c27aa6f37e2445", size = 8387095, upload-time = "2025-10-29T19:40:18.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/2b/cb12cbc7db1ba310aa7b1de9b4e018576f653105993736c086ee67d2ec02/ty-0.0.1a25-py3-none-win_amd64.whl", hash = "sha256:a2fad3d8e92bb4d57a8872a6f56b1aef54539d36f23ebb01abe88ac4338efafb", size = 9059225, upload-time = "2025-10-29T19:40:20.278Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/c1/f6be8cdd0bf387c1d8ee9d14bb299b7b5d2c0532f550a6693216a32ec0c5/ty-0.0.1a25-py3-none-win_arm64.whl", hash = "sha256:dde2962d448ed87c48736e9a4bb13715a4cced705525e732b1c0dac1d4c66e3d", size = 8536832, upload-time = "2025-10-29T19:40:22.014Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2171,20 +2137,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.33.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "distlib" },
|
||||
{ name = "filelock" },
|
||||
{ name = "platformdirs" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8b/60/4f20960df6c7b363a18a55ab034c8f2bcd5d9770d1f94f9370ec104c1855/virtualenv-20.33.1.tar.gz", hash = "sha256:1b44478d9e261b3fb8baa5e74a0ca3bc0e05f21aa36167bf9cbf850e542765b8", size = 6082160, upload-time = "2025-08-05T16:10:55.605Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/ff/ded57ac5ff40a09e6e198550bab075d780941e0b0f83cbeabd087c59383a/virtualenv-20.33.1-py3-none-any.whl", hash = "sha256:07c19bc66c11acab6a5958b815cbcee30891cd1c2ccf53785a28651a0d8d8a67", size = 6060362, upload-time = "2025-08-05T16:10:52.81Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.2.13"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue