Merge branch 'main' into claude/issue-1625-20250826-0138

This commit is contained in:
William Easton 2025-08-26 16:22:40 -05:00 committed by GitHub
commit 36760418c2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 1505 additions and 336 deletions

View file

@ -376,10 +376,31 @@
"group": "fastmcp.utilities",
"pages": [
"python-sdk/fastmcp-utilities-__init__",
"python-sdk/fastmcp-utilities-auth",
"python-sdk/fastmcp-utilities-cli",
"python-sdk/fastmcp-utilities-components",
"python-sdk/fastmcp-utilities-exceptions",
"python-sdk/fastmcp-utilities-fastmcp_config",
{
"group": "fastmcp_config",
"pages": [
"python-sdk/fastmcp-utilities-fastmcp_config-__init__",
{
"group": "v1",
"pages": [
"python-sdk/fastmcp-utilities-fastmcp_config-v1-__init__",
"python-sdk/fastmcp-utilities-fastmcp_config-v1-fastmcp_config",
{
"group": "sources",
"pages": [
"python-sdk/fastmcp-utilities-fastmcp_config-v1-sources-__init__",
"python-sdk/fastmcp-utilities-fastmcp_config-v1-sources-base",
"python-sdk/fastmcp-utilities-fastmcp_config-v1-sources-filesystem"
]
}
]
}
]
},
"python-sdk/fastmcp-utilities-http",
"python-sdk/fastmcp-utilities-inspect",
"python-sdk/fastmcp-utilities-json_schema",

View file

@ -212,7 +212,7 @@ Redirect path configured in your Azure App registration
</ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_REQUIRED_SCOPES" default='["User.Read", "email", "openid", "profile"]'>
Comma-separated list of required Microsoft Graph scopes
Comma-, space-, or JSON-separated list of required Microsoft Graph scopes
</ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_AZURE_TIMEOUT_SECONDS" default="10">

View file

@ -169,7 +169,7 @@ Redirect path configured in your GitHub OAuth App
</ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_GITHUB_REQUIRED_SCOPES" default='["user"]'>
Comma-separated list of required GitHub scopes (e.g., `user,repo`)
Comma-, space-, or JSON-separated list of required GitHub scopes (e.g., `user repo` or `["user","repo"]`)
</ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_GITHUB_TIMEOUT_SECONDS" default="10">

View file

@ -179,7 +179,7 @@ Redirect path configured in your Google OAuth Client
</ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_GOOGLE_REQUIRED_SCOPES" default="[]">
Comma-separated list of required Google scopes (e.g., `openid`)
Comma-, space-, or JSON-separated list of required Google scopes (e.g., `openid,profile` or `["openid","profile"]`)
</ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_GOOGLE_TIMEOUT_SECONDS" default="10">

View file

@ -166,7 +166,7 @@ Redirect path configured in your WorkOS OAuth App
</ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_WORKOS_REQUIRED_SCOPES" default="[]">
List of required OAuth scopes (e.g., `["openid", "profile", "email"]`)
Comma-, space-, or JSON-separated list of required OAuth scopes (e.g., `openid profile email` or `["openid","profile","email"]`)
</ParamField>
<ParamField path="FASTMCP_SERVER_AUTH_WORKOS_TIMEOUT_SECONDS" default="10">

View file

@ -436,17 +436,45 @@ To use this configuration with your MCP client, you'll typically need to add it
<VersionBadge version="2.9.0" />
Generate a detailed JSON report about a FastMCP server, including information about its tools, prompts, resources, and capabilities.
Inspect a FastMCP server to view summary information or generate a detailed JSON report.
```bash
# Show text summary
fastmcp inspect server.py
# Output FastMCP JSON to stdout
fastmcp inspect server.py --format fastmcp
# Save MCP JSON to file (format required with -o)
fastmcp inspect server.py --format mcp -o manifest.json
```
### Options
| Option | Flag | Description |
| ------ | ---- | ----------- |
| Output File | `--output`, `-o` | Output file path for the JSON report (default: server-info.json) |
| Format | `--format`, `-f` | Output format: `fastmcp` (FastMCP-specific) or `mcp` (MCP protocol). Required when using `-o` |
| Output File | `--output`, `-o` | Save JSON report to file instead of stdout. Requires `--format` |
### Output Formats
#### FastMCP Format (`--format fastmcp`)
The default and most comprehensive format, includes all FastMCP-specific metadata:
- Server name, instructions, and version
- FastMCP version and MCP version
- Tool tags and enabled status
- Output schemas for tools
- Annotations and custom metadata
- Uses snake_case field names
- **Use this for**: Complete server introspection and debugging FastMCP servers
#### MCP Protocol Format (`--format mcp`)
Shows exactly what MCP clients will see via the protocol:
- Only includes standard MCP protocol fields
- Matches output from `client.list_tools()`, `client.list_prompts()`, etc.
- Uses camelCase field names (e.g., `inputSchema`)
- Excludes FastMCP-specific fields like tags and enabled status
- **Use this for**: Debugging client visibility and ensuring MCP compatibility
### Entrypoints
@ -461,17 +489,46 @@ The `inspect` command supports local FastMCP server files and configuration:
The `inspect` command **only supports local files and fastmcp.json** - no URLs, remote servers, or standard MCP configuration files.
</Warning>
**Examples**
### Examples
```bash
# Auto-detect server entrypoint
# Show text summary (no JSON output)
fastmcp inspect server.py
# Output:
# Server: MyServer
# Instructions: A helpful MCP server
# Version: 1.0.0
#
# Components:
# Tools: 5
# Prompts: 2
# Resources: 3
# Templates: 1
#
# Environment:
# FastMCP: 2.0.0
# MCP: 1.0.0
#
# Use --format [fastmcp|mcp] for complete JSON output
# Output FastMCP format to stdout
fastmcp inspect server.py --format fastmcp
# Specify server entrypoint
fastmcp inspect server.py:my_server
# Custom output location
fastmcp inspect server.py --output analysis.json
# Output MCP protocol format to stdout
fastmcp inspect server.py --format mcp
# Save to file (format required)
fastmcp inspect server.py --format fastmcp -o server-manifest.json
# Save MCP format with custom server object
fastmcp inspect server.py:my_server --format mcp -o mcp-manifest.json
# Error: format required with output file
fastmcp inspect server.py -o output.json
# Error: --format is required when using -o/--output
```
## `fastmcp project prepare`

View file

@ -243,17 +243,25 @@
"editable": {
"anyOf": [
{
"type": "string"
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Directory to install in editable mode",
"description": "Directories to install in editable mode",
"examples": [
".",
"../my-package"
[
".",
"../my-package"
],
[
"/path/to/package"
]
],
"title": "Editable"
}

View file

@ -243,17 +243,25 @@
"editable": {
"anyOf": [
{
"type": "string"
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Directory to install in editable mode",
"description": "Directories to install in editable mode",
"examples": [
".",
"../my-package"
[
".",
"../my-package"
],
[
"/path/to/package"
]
],
"title": "Editable"
}

View file

@ -32,7 +32,7 @@ Add or update a FastMCP server in Claude's configuration.
**Args:**
- `file_spec`: Path to the server file, optionally with \:object suffix
- `server_name`: Name for the server in Claude's config
- `with_editable`: Optional directory to install in editable mode
- `with_editable`: Optional list of directories to install in editable mode
- `with_packages`: Optional list of additional packages to install
- `env_vars`: Optional dictionary of environment variables. These are merged with
any existing variables, with new values taking precedence.

View file

@ -10,7 +10,24 @@ FastMCP CLI tools using Cyclopts.
## Functions
### `version` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L63" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `with_argv` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L70" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
with_argv(args: list[str] | None)
```
Temporarily replace sys.argv if args provided.
This context manager is used at the CLI boundary to inject
server arguments when needed, without mutating sys.argv deep
in the source loading logic.
Args are provided without the script name, so we preserve sys.argv[0]
and replace the rest.
### `version` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L93" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
version()
@ -20,7 +37,7 @@ version()
Display version information and platform details.
### `dev` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L101" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `dev` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L131" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
dev(server_spec: str | None = None) -> None
@ -33,7 +50,7 @@ Run an MCP server with the MCP Inspector for development.
- `server_spec`: Python file to run, optionally with \:object suffix, or None to auto-detect fastmcp.json
### `run` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L319" 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/cli/cli.py#L349" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
run(server_spec: str | None = None, *server_args: str) -> None
@ -57,28 +74,56 @@ fastmcp run server.py -- --config config.json --debug
- `server_spec`: Python file, object specification (file\:obj), config file, URL, or None to auto-detect
### `inspect` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L582" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `inspect` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/cli.py#L622" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
inspect(server_spec: str | None = None) -> None
```
Inspect an MCP server and generate a JSON report.
Inspect an MCP server and display information or generate a JSON report.
This command analyzes an MCP server and generates a comprehensive JSON report
containing information about the server's name, instructions, version, tools,
prompts, resources, templates, and capabilities.
This command analyzes an MCP server. Without flags, it displays a text summary.
Use --format to output complete JSON data.
**Examples:**
# Show text summary
fastmcp inspect server.py
fastmcp inspect server.py -o report.json
fastmcp inspect server.py:mcp -o analysis.json
fastmcp inspect path/to/server.py:app -o /tmp/server-info.json
# Output FastMCP format JSON to stdout
fastmcp inspect server.py --format fastmcp
# Save MCP protocol format to file (format required with -o)
fastmcp inspect server.py --format mcp -o manifest.json
# Inspect from fastmcp.json configuration
fastmcp inspect fastmcp.json
fastmcp inspect # auto-detect fastmcp.json
**Args:**
- `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#L904" 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
```
Prepare a FastMCP project by creating a persistent uv environment.
This command creates a persistent uv project with all dependencies installed:
- Creates a pyproject.toml with dependencies from the config
- Installs all Python packages into a .venv
- Prepares the source (git clone, download, etc.) unless --skip-source
After running this command, you can use:
fastmcp run &lt;config&gt; --project &lt;output-dir&gt;
This is useful for:
- CI/CD pipelines with separate build and run stages
- Docker images where you prepare during build
- Production deployments where you want fast startup times

View file

@ -46,7 +46,7 @@ Install FastMCP server in Claude Code.
- `file`: Path to the server file
- `server_object`: Optional server object name (for \:object suffix)
- `name`: Name for the server in Claude Code
- `with_editable`: Optional directory to install in editable mode
- `with_editable`: Optional list of directories to install in editable mode
- `with_packages`: Optional list of additional packages to install
- `env_vars`: Optional dictionary of environment variables
- `python_version`: Optional Python version to use

View file

@ -33,7 +33,7 @@ Install FastMCP server in Claude Desktop.
- `file`: Path to the server file
- `server_object`: Optional server object name (for \:object suffix)
- `name`: Name for the server in Claude's config
- `with_editable`: Optional directory to install in editable mode
- `with_editable`: Optional list of directories to install in editable mode
- `with_packages`: Optional list of additional packages to install
- `env_vars`: Optional dictionary of environment variables
- `python_version`: Optional Python version to use

View file

@ -57,7 +57,7 @@ Install FastMCP server to workspace-specific Cursor configuration.
- `server_object`: Optional server object name (for \:object suffix)
- `name`: Name for the server in Cursor
- `workspace_path`: Path to the workspace directory
- `with_editable`: Optional directory to install in editable mode
- `with_editable`: Optional list of directories to install in editable mode
- `with_packages`: Optional list of additional packages to install
- `env_vars`: Optional dictionary of environment variables
- `python_version`: Optional Python version to use
@ -81,7 +81,7 @@ Install FastMCP server in Cursor.
- `file`: Path to the server file
- `server_object`: Optional server object name (for \:object suffix)
- `name`: Name for the server in Cursor
- `with_editable`: Optional directory to install in editable mode
- `with_editable`: Optional list of directories to install in editable mode
- `with_packages`: Optional list of additional packages to install
- `env_vars`: Optional dictionary of environment variables
- `python_version`: Optional Python version to use

View file

@ -23,7 +23,7 @@ Generate MCP configuration JSON for manual installation.
- `file`: Path to the server file
- `server_object`: Optional server object name (for \:object suffix)
- `name`: Name for the server in MCP config
- `with_editable`: Optional directory to install in editable mode
- `with_editable`: Optional list of directories to install in editable mode
- `with_packages`: Optional list of additional packages to install
- `env_vars`: Optional dictionary of environment variables
- `copy`: If True, copy to clipboard instead of printing to stdout

View file

@ -23,7 +23,7 @@ Parse environment variable string in format KEY=VALUE.
### `process_common_args` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/install/shared.py#L29" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
process_common_args(server_spec: str, server_name: str | None, with_packages: list[str], env_vars: list[str], env_file: Path | None) -> tuple[Path, str | None, str, list[str], dict[str, str] | None]
process_common_args(server_spec: str, server_name: str | None, with_packages: list[str] | None, env_vars: list[str] | None, env_file: Path | None) -> tuple[Path, str | None, str, list[str], dict[str, str] | None]
```

View file

@ -10,7 +10,7 @@ FastMCP run command implementation with enhanced type hints.
## Functions
### `is_url` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L32" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `is_url` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L29" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
is_url(path: str) -> bool
@ -20,43 +20,10 @@ is_url(path: str) -> bool
Check if a string is a URL.
### `parse_file_path` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L38" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `run_with_uv` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L35" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
parse_file_path(server_spec: str) -> tuple[Path, str | None]
```
Parse a file path that may include a server object specification.
**Args:**
- `server_spec`: Path to file, optionally with \:object suffix
**Returns:**
- Tuple of (file_path, server_object)
### `import_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L69" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
import_server(file: Path, server_or_factory: str | None = None) -> Any
```
Import a MCP server from a file.
**Args:**
- `file`: Path to the file
- `server_or_factory`: Optional object name in format "module\:object" or just "object"
**Returns:**
- The server object (or result of calling a factory function)
### `run_with_uv` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L182" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
run_with_uv(server_spec: str, python_version: str | None = None, with_packages: list[str] | None = None, with_requirements: Path | None = None, project: Path | None = None, transport: TransportType | None = None, host: str | None = None, port: int | None = None, path: str | None = None, log_level: LogLevelType | None = None, show_banner: bool = True, editable: str | None = None) -> None
run_with_uv(server_spec: str, python_version: str | None = None, with_packages: list[str] | None = None, with_requirements: Path | None = None, project: Path | None = None, transport: TransportType | None = None, host: str | None = None, port: int | None = None, path: str | None = None, log_level: LogLevelType | None = None, show_banner: bool = True, editable: str | list[str] | None = None) -> None
```
@ -76,7 +43,7 @@ Run a MCP server using uv run subprocess.
- `show_banner`: Whether to show the server banner
### `create_client_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L313" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `create_client_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L170" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
create_client_server(url: str) -> Any
@ -92,7 +59,7 @@ Create a FastMCP server from a client URL.
- A FastMCP server instance
### `create_mcp_config_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L333" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `create_mcp_config_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L190" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
create_mcp_config_server(mcp_config_path: Path) -> FastMCP[None]
@ -102,7 +69,7 @@ create_mcp_config_server(mcp_config_path: Path) -> FastMCP[None]
Create a FastMCP server from a MCPConfig.
### `load_fastmcp_config` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L344" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `load_fastmcp_config` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L201" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
load_fastmcp_config(config_path: Path) -> FastMCPConfig
@ -118,28 +85,10 @@ Load a FastMCP configuration from a fastmcp.json file.
- FastMCPConfig object
### `import_server_with_args` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L362" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `run_command` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L219" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
import_server_with_args(file: Path, server_or_factory: str | None = None, server_args: list[str] | None = None) -> Any
```
Import a server with optional command line arguments.
**Args:**
- `file`: Path to the server file
- `server_or_factory`: Optional server object or factory function name
- `server_args`: Optional command line arguments to inject
**Returns:**
- The imported server object
### `run_command` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L388" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
run_command(server_spec: str, transport: TransportType | None = None, host: str | None = None, port: int | None = None, path: str | None = None, log_level: LogLevelType | None = None, server_args: list[str] | None = None, show_banner: bool = True, use_direct_import: bool = False) -> None
run_command(server_spec: str, transport: TransportType | None = None, host: str | None = None, port: int | None = None, path: str | None = None, log_level: LogLevelType | None = None, server_args: list[str] | None = None, show_banner: bool = True, use_direct_import: bool = False, skip_source: bool = False) -> None
```
@ -155,9 +104,10 @@ Run a MCP server or connect to a remote one.
- `server_args`: Additional arguments to pass to the server
- `show_banner`: Whether to show the server banner
- `use_direct_import`: Whether to use direct import instead of subprocess
- `skip_source`: Whether to skip source preparation step
### `run_v1_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L496" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `run_v1_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/cli/run.py#L341" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
run_v1_server(server: FastMCP1x, host: str | None = None, port: int | None = None, transport: TransportType | None = None) -> None

View file

@ -226,7 +226,7 @@ The flow:
4. When client reconnects with a different port, ProxyDCRClient accepts it
#### `authorize` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L444" 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#L435" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str
@ -240,7 +240,7 @@ This implements the DCR-compliant proxy pattern:
3. Redirect to IdP with our fixed callback URL
#### `load_authorization_code` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L501" 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#L491" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
load_authorization_code(self, client: OAuthClientInformationFull, authorization_code: str) -> AuthorizationCode | None
@ -252,7 +252,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#L543" 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#L533" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
exchange_authorization_code(self, client: OAuthClientInformationFull, authorization_code: AuthorizationCode) -> OAuthToken
@ -264,7 +264,7 @@ For the DCR-compliant proxy flow, we return the IdP tokens that were obtained
during the IdP callback exchange. PKCE validation is handled by the MCP framework.
#### `load_refresh_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L610" 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#L600" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
load_refresh_token(self, client: OAuthClientInformationFull, refresh_token: str) -> RefreshToken | None
@ -273,7 +273,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#L618" 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#L608" 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
@ -282,7 +282,7 @@ exchange_refresh_token(self, client: OAuthClientInformationFull, refresh_token:
Exchange refresh token for new access token using authlib.
#### `load_access_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L693" 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#L683" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
load_access_token(self, token: str) -> AccessToken | None
@ -294,7 +294,7 @@ Delegates to the JWT verifier which handles signature validation,
expiration checking, and claims validation using the upstream JWKS.
#### `revoke_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L710" 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#L700" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
revoke_token(self, token: AccessToken | RefreshToken) -> None
@ -306,7 +306,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#L903" 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#L893" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_routes(self) -> list[Route]

View file

@ -14,13 +14,13 @@ using the OAuth Proxy pattern for non-DCR OAuth flows.
## Classes
### `AzureProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L22" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `AzureProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L23" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Settings for Azure OAuth provider.
### `AzureTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L40" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `AzureTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L46" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Token verifier for Azure OAuth tokens.
@ -31,7 +31,7 @@ to get user information and validate the token.
**Methods:**
#### `verify_token` <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>
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L68" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
verify_token(self, token: str) -> AccessToken | None
@ -40,7 +40,7 @@ verify_token(self, token: str) -> AccessToken | None
Verify Azure OAuth token by calling Microsoft Graph API.
### `AzureProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L112" 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#L118" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Azure (Microsoft Entra) OAuth provider for FastMCP.

View file

@ -29,13 +29,13 @@ Example:
## Classes
### `GitHubProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L38" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `GitHubProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L39" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Settings for GitHub OAuth provider.
### `GitHubTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L55" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `GitHubTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L61" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Token verifier for GitHub OAuth tokens.
@ -46,7 +46,7 @@ by calling GitHub's API to check if they're valid and get user info.
**Methods:**
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L77" 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/github.py#L83" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
verify_token(self, token: str) -> AccessToken | None
@ -55,7 +55,7 @@ verify_token(self, token: str) -> AccessToken | None
Verify GitHub OAuth token by calling GitHub API.
### `GitHubProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L161" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `GitHubProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/github.py#L167" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Complete GitHub OAuth provider for FastMCP.

View file

@ -29,13 +29,13 @@ Example:
## Classes
### `GoogleProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L40" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `GoogleProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L41" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Settings for Google OAuth provider.
### `GoogleTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L57" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `GoogleTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L63" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Token verifier for Google OAuth tokens.
@ -46,7 +46,7 @@ by calling Google's tokeninfo API to check if they're valid and get user info.
**Methods:**
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L79" 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/google.py#L85" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
verify_token(self, token: str) -> AccessToken | None
@ -55,7 +55,7 @@ verify_token(self, token: str) -> AccessToken | None
Verify Google OAuth token by calling Google's tokeninfo API.
### `GoogleProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L177" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `GoogleProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/google.py#L183" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Complete Google OAuth provider for FastMCP.

View file

@ -10,19 +10,19 @@ TokenVerifier implementations for FastMCP.
## Classes
### `JWKData` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L26" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `JWKData` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L27" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
JSON Web Key data structure.
### `JWKSData` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L39" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `JWKSData` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L40" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
JSON Web Key Set data structure.
### `RSAKeyPair` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L46" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `RSAKeyPair` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L47" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
RSA key pair for JWT testing.
@ -30,7 +30,7 @@ RSA key pair for JWT testing.
**Methods:**
#### `generate` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L53" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `generate` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L54" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
generate(cls) -> RSAKeyPair
@ -42,7 +42,7 @@ Generate an RSA key pair for testing.
- Generated key pair
#### `create_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L88" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `create_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L89" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
create_token(self, subject: str = 'fastmcp-user', issuer: str = 'https://fastmcp.example.com', audience: str | list[str] | None = None, scopes: list[str] | None = None, expires_in_seconds: int = 3600, additional_claims: dict[str, Any] | None = None, kid: str | None = None) -> str
@ -60,13 +60,13 @@ Generate a test JWT token for testing purposes.
- `kid`: Key ID to include in header
### `JWTVerifierSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L141" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `JWTVerifierSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L142" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Settings for JWT token verification.
### `JWTVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L160" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `JWTVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L166" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
JWT token verifier supporting both asymmetric (RSA/ECDSA) and symmetric (HMAC) algorithms.
@ -88,7 +88,7 @@ Use this when:
**Methods:**
#### `load_access_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L362" 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/providers/jwt.py#L368" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
load_access_token(self, token: str) -> AccessToken | None
@ -103,7 +103,7 @@ Validates the provided JWT bearer token.
- AccessToken object if valid, None if invalid or expired
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L464" 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#L470" 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#L480" 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#L486" 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#L514" 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#L520" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
verify_token(self, token: str) -> AccessToken | None

View file

@ -18,13 +18,13 @@ Choose based on your WorkOS setup and authentication requirements.
## Classes
### `WorkOSProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L29" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `WorkOSProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L30" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Settings for WorkOS OAuth provider.
### `WorkOSTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L47" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `WorkOSTokenVerifier` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L53" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Token verifier for WorkOS OAuth tokens.
@ -35,7 +35,7 @@ the /oauth2/userinfo endpoint to check validity and get user info.
**Methods:**
#### `verify_token` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L72" 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/workos.py#L78" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
verify_token(self, token: str) -> AccessToken | None
@ -44,7 +44,7 @@ verify_token(self, token: str) -> AccessToken | None
Verify WorkOS OAuth token by calling userinfo endpoint.
### `WorkOSProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L120" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `WorkOSProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L126" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Complete WorkOS OAuth provider for FastMCP.
@ -65,9 +65,9 @@ Setup Requirements:
4. Note your Client ID and Client Secret
### `AuthKitProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L247" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `AuthKitProviderSettings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L253" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `AuthKitProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L260" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `AuthKitProvider` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L271" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
AuthKit metadata provider for DCR (Dynamic Client Registration).
@ -93,7 +93,7 @@ https://workos.com/docs/authkit/mcp/integrating/token-verification
**Methods:**
#### `get_routes` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L343" 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/providers/workos.py#L354" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_routes(self) -> list[Route]

View file

@ -7,7 +7,7 @@ sidebarTitle: elicitation
## Functions
### `get_elicitation_schema` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/elicitation.py#L42" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `get_elicitation_schema` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/elicitation.py#L98" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
get_elicitation_schema(response_type: type[T]) -> dict[str, Any]
@ -20,7 +20,7 @@ Get the schema for an elicitation response.
- `response_type`: The type of the response
### `validate_elicitation_json_schema` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/elicitation.py#L58" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `validate_elicitation_json_schema` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/elicitation.py#L117" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
validate_elicitation_json_schema(schema: dict[str, Any]) -> None
@ -45,10 +45,43 @@ This ensures the schema is compatible with MCP elicitation requirements:
## Classes
### `AcceptedElicitation` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/elicitation.py#L30" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `ElicitationJsonSchema` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/elicitation.py#L31" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Custom JSON schema generator for MCP elicitation that always inlines enums.
MCP elicitation requires inline enum schemas without $ref/$defs references.
This generator ensures enums are always generated inline for compatibility.
Optionally adds enumNames for better UI display when available.
**Methods:**
#### `generate_inner` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/elicitation.py#L39" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
generate_inner(self, schema: core_schema.CoreSchema) -> JsonSchemaValue
```
Override to prevent ref generation for enums.
#### `enum_schema` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/elicitation.py#L49" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
enum_schema(self, schema: core_schema.EnumSchema) -> JsonSchemaValue
```
Generate inline enum schema with optional enumNames for better UI.
If enum members have a _display_name_ attribute or custom __str__,
we'll include enumNames for better UI representation.
### `AcceptedElicitation` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/elicitation.py#L86" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Result when user accepts the elicitation.
### `ScalarElicitationType` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/elicitation.py#L38" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `ScalarElicitationType` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/elicitation.py#L94" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

View file

@ -62,7 +62,7 @@ y=2)` will call the parent tool with `x=1` and `y=2`.
- `RuntimeError`: If called outside a transformed tool context.
### `apply_transformations_to_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_transform.py#L915" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `apply_transformations_to_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_transform.py#L933" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
apply_transformations_to_tools(tools: dict[str, Tool], transformations: dict[str, ToolTransformConfig]) -> dict[str, Tool]
@ -272,7 +272,7 @@ async def custom_output(**kwargs) -> ToolResult:
```
### `ToolTransformConfig` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_transform.py#L869" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `ToolTransformConfig` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_transform.py#L887" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Provides a way to transform a tool.
@ -280,7 +280,7 @@ Provides a way to transform a tool.
**Methods:**
#### `apply` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_transform.py#L901" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
#### `apply` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/tools/tool_transform.py#L919" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
apply(self, tool: Tool) -> TransformedTool

View file

@ -0,0 +1,25 @@
---
title: auth
sidebarTitle: auth
---
# `fastmcp.utilities.auth`
Authentication utility helpers.
## Functions
### `parse_scopes` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/auth.py#L9" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
parse_scopes(value: Any) -> list[str] | None
```
Parse scopes from environment variables or settings values.
Accepts either a JSON array string, a comma- or space-separated string,
a list of strings, or ``None``. Returns a list of scopes or ``None`` if
no value is provided.

View file

@ -1,6 +1,6 @@
---
title: fastmcp_config
sidebarTitle: fastmcp_config
title: __init__
sidebarTitle: __init__
---
# `fastmcp.utilities.fastmcp_config`

View file

@ -0,0 +1,8 @@
---
title: __init__
sidebarTitle: __init__
---
# `fastmcp.utilities.fastmcp_config.v1`
*This module is empty or contains only private/internal implementations.*

View file

@ -0,0 +1,297 @@
---
title: fastmcp_config
sidebarTitle: fastmcp_config
---
# `fastmcp.utilities.fastmcp_config.v1.fastmcp_config`
FastMCP Configuration File Support.
This module provides support for fastmcp.json configuration files that allow
users to specify server settings in a declarative format instead of using
command-line arguments.
## Functions
### `generate_schema` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/fastmcp_config.py#L707" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
generate_schema(output_path: Path | str | None = None) -> dict[str, Any] | None
```
Generate JSON schema for fastmcp.json files.
This is used to create the schema file that IDEs can use for
validation and auto-completion.
**Args:**
- `output_path`: Optional path to write the schema to. If provided,
writes the schema and returns None. If not provided,
returns the schema as a dictionary.
**Returns:**
- JSON schema as a dictionary if output_path is None, otherwise None
## Classes
### `Environment` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/fastmcp_config.py#L33" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Configuration for Python environment setup.
**Methods:**
#### `build_uv_args` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/fastmcp_config.py#L66" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
build_uv_args(self, command: str | list[str] | None = None) -> list[str]
```
Build uv run arguments from this environment configuration.
**Args:**
- `command`: Optional command to append (string or list of args)
**Returns:**
- List of arguments for uv run command
#### `run_with_uv` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/fastmcp_config.py#L109" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
run_with_uv(self, command: list[str]) -> None
```
Execute a command using uv run with this environment configuration.
**Args:**
- `command`: Command and arguments to execute (e.g., ["fastmcp", "run", "server.py"])
#### `needs_uv` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/fastmcp_config.py#L132" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
needs_uv(self) -> bool
```
Check if this environment config requires uv to set up.
**Returns:**
- True if any environment settings require uv run
#### `prepare` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/fastmcp_config.py#L148" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
prepare(self, output_dir: Path | None = None) -> None
```
Prepare the Python environment using uv.
**Args:**
- `output_dir`: Directory where the persistent uv project will be created.
If None, creates a temporary directory for ephemeral use.
### `Deployment` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/fastmcp_config.py#L313" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Configuration for server deployment and runtime settings.
**Methods:**
#### `apply_runtime_settings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/fastmcp_config.py#L362" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
apply_runtime_settings(self, config_path: Path | None = None) -> None
```
Apply runtime settings like environment variables and working directory.
**Args:**
- `config_path`: Path to config file for resolving relative paths
Environment variables support interpolation with ${VAR_NAME} syntax.
For example: "API_URL": "https://api.${ENVIRONMENT}.example.com"
will substitute the value of the ENVIRONMENT variable at runtime.
### `FastMCPConfig` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/fastmcp_config.py#L411" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Configuration for a FastMCP server.
This configuration file allows you to specify all settings needed to run
a FastMCP server in a declarative format.
**Methods:**
#### `validate_source` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/fastmcp_config.py#L460" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
validate_source(cls, v: dict | FileSystemSource) -> FileSystemSource
```
Validate and convert source to proper format.
Supports:
- Dict format: {"path": "server.py", "entrypoint": "app"}
- FileSystemSource instance (passed through)
No string parsing happens here - that's only at CLI boundaries.
FastMCPConfig works only with properly typed objects.
#### `validate_environment` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/fastmcp_config.py#L483" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
validate_environment(cls, v: dict | Environment) -> Environment
```
Validate and convert environment to Environment.
Accepts:
- Environment instance
- dict that can be converted to Environment
#### `validate_deployment` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/fastmcp_config.py#L499" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
validate_deployment(cls, v: dict | Deployment) -> Deployment
```
Validate and convert deployment to Deployment.
Accepts:
- Deployment instance
- dict that can be converted to Deployment
#### `from_file` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/fastmcp_config.py#L515" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
from_file(cls, file_path: Path) -> FastMCPConfig
```
Load configuration from a JSON file.
**Args:**
- `file_path`: Path to the configuration file
**Returns:**
- FastMCPConfig instance
**Raises:**
- `FileNotFoundError`: If the file doesn't exist
- `json.JSONDecodeError`: If the file is not valid JSON
- `pydantic.ValidationError`: If the configuration is invalid
#### `from_cli_args` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/fastmcp_config.py#L538" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
from_cli_args(cls, source: FileSystemSource, transport: Literal['stdio', 'http', 'sse', 'streamable-http'] | None = None, host: str | None = None, port: int | None = None, path: str | None = None, log_level: Literal['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] | None = None, python: str | None = None, dependencies: list[str] | None = None, requirements: str | None = None, project: str | None = None, editable: str | None = None, env: dict[str, str] | None = None, cwd: str | None = None, args: list[str] | None = None) -> FastMCPConfig
```
Create a config from CLI arguments.
This allows us to have a single code path where everything
goes through a config object.
**Args:**
- `source`: Server source (FileSystemSource instance)
- `transport`: Transport protocol
- `host`: Host for HTTP transport
- `port`: Port for HTTP transport
- `path`: URL path for server
- `log_level`: Logging level
- `python`: Python version
- `dependencies`: Python packages to install
- `requirements`: Path to requirements file
- `project`: Path to project directory
- `editable`: Path to install in editable mode
- `env`: Environment variables
- `cwd`: Working directory
- `args`: Server arguments
**Returns:**
- FastMCPConfig instance
#### `find_config` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/fastmcp_config.py#L615" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
find_config(cls, start_path: Path | None = None) -> Path | None
```
Find a fastmcp.json file in the specified directory.
**Args:**
- `start_path`: Directory to look in (defaults to current directory)
**Returns:**
- Path to the configuration file, or None if not found
#### `prepare` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/fastmcp_config.py#L634" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
prepare(self, skip_source: bool = False, output_dir: Path | None = None) -> None
```
Prepare environment and source for execution.
When output_dir is provided, creates a persistent uv project.
When output_dir is None, does ephemeral caching (for backwards compatibility).
**Args:**
- `skip_source`: Skip source preparation if True
- `output_dir`: Directory to create the persistent uv project in (optional)
#### `prepare_environment` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/fastmcp_config.py#L655" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
prepare_environment(self, output_dir: Path | None = None) -> None
```
Prepare the Python environment.
**Args:**
- `output_dir`: If provided, creates a persistent uv project in this directory.
If None, just populates uv's cache for ephemeral use.
Delegates to the environment's prepare() method
#### `prepare_source` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/fastmcp_config.py#L666" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
prepare_source(self) -> None
```
Prepare the source for loading.
Delegates to the source's prepare() method.
#### `run_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/fastmcp_config.py#L673" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
run_server(self, **kwargs: Any) -> None
```
Load and run the server with this configuration.
**Args:**
- `**kwargs`: Additional arguments to pass to server.run_async()
These override config settings

View file

@ -0,0 +1,8 @@
---
title: __init__
sidebarTitle: __init__
---
# `fastmcp.utilities.fastmcp_config.v1.sources`
*This module is empty or contains only private/internal implementations.*

View file

@ -0,0 +1,42 @@
---
title: base
sidebarTitle: base
---
# `fastmcp.utilities.fastmcp_config.v1.sources.base`
## Classes
### `BaseSource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/sources/base.py#L7" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Abstract base class for all source types.
**Methods:**
#### `prepare` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/sources/base.py#L12" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
prepare(self) -> None
```
Prepare the source (download, clone, install, etc).
For sources that need preparation (e.g., git clone, download),
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/fastmcp_config/v1/sources/base.py#L23" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
load_server(self) -> Any
```
Load and return the FastMCP server instance.
Must be called after prepare() if the source requires preparation.
All information needed to load the server should be available
as attributes on the source instance.

View file

@ -0,0 +1,37 @@
---
title: filesystem
sidebarTitle: filesystem
---
# `fastmcp.utilities.fastmcp_config.v1.sources.filesystem`
## Classes
### `FileSystemSource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/sources/filesystem.py#L15" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Source for local Python files.
**Methods:**
#### `parse_path_with_object` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/sources/filesystem.py#L27" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
parse_path_with_object(cls, v: str) -> str
```
Parse path:object syntax and extract the object name.
This validator runs before the model is created, allowing us to
handle the "file.py:object" syntax at the model boundary.
#### `load_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/fastmcp_config/v1/sources/filesystem.py#L62" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
load_server(self) -> Any
```
Load server from filesystem.

View file

@ -10,7 +10,7 @@ Utilities for inspecting FastMCP instances.
## Functions
### `inspect_fastmcp_v2` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L83" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `inspect_fastmcp_v2` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L98" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo
@ -26,7 +26,7 @@ Extract information from a FastMCP v2.x instance.
- FastMCPInfo dataclass containing the extracted information
### `inspect_fastmcp_v1` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L185" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `inspect_fastmcp_v1` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L215" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo
@ -42,7 +42,7 @@ Extract information from a FastMCP v1.x instance using a Client.
- FastMCPInfo dataclass containing the extracted information
### `inspect_fastmcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L306" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `inspect_fastmcp` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L336" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
inspect_fastmcp(mcp: FastMCP[Any] | FastMCP1x) -> FastMCPInfo
@ -61,34 +61,83 @@ and uses the appropriate extraction method.
- FastMCPInfo dataclass containing the extracted information
### `format_fastmcp_info` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L361" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
format_fastmcp_info(info: FastMCPInfo) -> bytes
```
Format FastMCPInfo as FastMCP-specific JSON.
This includes FastMCP-specific fields like tags, enabled, annotations, etc.
### `format_mcp_info` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L388" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
format_mcp_info(mcp: FastMCP[Any] | FastMCP1x) -> bytes
```
Format server info as standard MCP protocol JSON.
Uses Client to get the standard MCP protocol format with camelCase fields.
Includes version metadata at the top level.
### `format_info` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L421" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
```python
format_info(mcp: FastMCP[Any] | FastMCP1x, format: InspectFormat | Literal['fastmcp', 'mcp'], info: FastMCPInfo | None = None) -> bytes
```
Format server information according to the specified format.
**Args:**
- `mcp`: The FastMCP instance
- `format`: Output format ("fastmcp" or "mcp")
- `info`: Pre-extracted FastMCPInfo (optional, will be extracted if not provided)
**Returns:**
- JSON bytes in the requested format
## Classes
### `ToolInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L17" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `ToolInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L19" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Information about a tool.
### `PromptInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L30" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `PromptInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L35" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Information about a prompt.
### `ResourceInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L42" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `ResourceInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L49" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Information about a resource.
### `TemplateInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L55" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `TemplateInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L65" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Information about a resource template.
### `FastMCPInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L68" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
### `FastMCPInfo` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L82" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Information extracted from a FastMCP instance.
### `InspectFormat` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/utilities/inspect.py#L354" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
Output format for inspect command.

View file

@ -34,7 +34,7 @@ def update_claude_config(
file_spec: str,
server_name: str,
*,
with_editable: Path | None = None,
with_editable: list[Path] | None = None,
with_packages: list[str] | None = None,
env_vars: dict[str, str] | None = None,
) -> bool:
@ -43,7 +43,7 @@ def update_claude_config(
Args:
file_spec: Path to the server file, optionally with :object suffix
server_name: Name for the server in Claude's config
with_editable: Optional directory to install in editable mode
with_editable: Optional list of directories to install in editable mode
with_packages: Optional list of additional packages to install
env_vars: Optional dictionary of environment variables. These are merged with
any existing variables, with new values taking precedence.
@ -101,7 +101,7 @@ def update_claude_config(
# Build uv run command using Environment.build_uv_args()
env_config = Environment(
dependencies=deduplicated_packages,
editable=[str(with_editable)] if with_editable else None,
editable=[str(p) for p in with_editable] if with_editable else None,
)
args = env_config.build_uv_args()

View file

@ -13,7 +13,7 @@ from typing import Annotated, Literal
import cyclopts
import pyperclip
from pydantic import TypeAdapter, ValidationError
from pydantic import ValidationError
from rich.console import Console
from rich.table import Table
@ -21,7 +21,13 @@ import fastmcp
from fastmcp.cli import run as run_module
from fastmcp.cli.install import install_app
from fastmcp.server.server import FastMCP
from fastmcp.utilities.inspect import FastMCPInfo, inspect_fastmcp
from fastmcp.utilities.fastmcp_config import Environment, FastMCPConfig
from fastmcp.utilities.fastmcp_config.v1.sources.filesystem import FileSystemSource
from fastmcp.utilities.inspect import (
InspectFormat,
format_info,
inspect_fastmcp,
)
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import get_cached_typeadapter
@ -126,20 +132,21 @@ async def dev(
server_spec: str | None = None,
*,
with_editable: Annotated[
Path | None,
list[Path] | None,
cyclopts.Parameter(
name=["--with-editable", "-e"],
help="Directory containing pyproject.toml to install in editable mode",
"--with-editable",
help="Directory containing pyproject.toml to install in editable mode (can be used multiple times)",
negative="",
),
] = None,
with_packages: Annotated[
list[str],
list[str] | None,
cyclopts.Parameter(
"--with",
help="Additional packages to install",
help="Additional packages to install (can be used multiple times)",
negative="",
),
] = [],
] = None,
inspector_version: Annotated[
str | None,
cyclopts.Parameter(
@ -188,6 +195,9 @@ async def dev(
Args:
server_spec: Python file to run, optionally with :object suffix, or None to auto-detect fastmcp.json
"""
# Convert None to empty lists for list parameters
with_editable = with_editable or []
with_packages = with_packages or []
from pathlib import Path
from fastmcp.utilities.fastmcp_config import FastMCPConfig
@ -229,13 +239,9 @@ async def dev(
if config.environment.requirements
else None
)
# Note: config.environment.editable is a list, but CLI only supports single path
# Take the first editable path if available
with_editable = with_editable or (
Path(config.environment.editable[0])
if config.environment.editable and config.environment.editable[0]
else None
)
# Merge editable paths from config with CLI args
if config.environment.editable and not with_editable:
with_editable = [Path(p) for p in config.environment.editable]
# Merge packages from both sources
if config.environment.dependencies:
@ -256,7 +262,7 @@ async def dev(
"Starting dev server",
extra={
"server_spec": server_spec,
"with_editable": str(with_editable) if with_editable else None,
"with_editable": [str(p) for p in with_editable] if with_editable else None,
"with_packages": with_packages,
"ui_port": ui_port,
"server_port": server_port,
@ -298,14 +304,12 @@ async def dev(
inspector_cmd += f"@{inspector_version}"
# Create Environment object from CLI args
from fastmcp.utilities.fastmcp_config import Environment
env_config = Environment(
python=python,
dependencies=with_packages if with_packages else None,
requirements=str(with_requirements) if with_requirements else None,
project=str(project) if project else None,
editable=[str(with_editable)] if with_editable else None,
editable=[str(p) for p in with_editable] if with_editable else None,
)
uv_cmd = ["uv"] + env_config.build_uv_args(["fastmcp", "run", server_spec])
@ -396,13 +400,13 @@ async def run(
),
] = None,
with_packages: Annotated[
list[str],
list[str] | None,
cyclopts.Parameter(
"--with",
help="Additional packages to install (can be used multiple times)",
negative="",
),
] = [],
] = None,
project: Annotated[
Path | None,
cyclopts.Parameter(
@ -450,6 +454,8 @@ async def run(
Args:
server_spec: Python file, object specification (file:obj), config file, URL, or None to auto-detect
"""
# Convert None to empty lists for list parameters
with_packages = with_packages or []
# Load configuration if needed
from pathlib import Path
@ -616,13 +622,20 @@ async def run(
async def inspect(
server_spec: str | None = None,
*,
format: Annotated[
InspectFormat | None,
cyclopts.Parameter(
name=["--format", "-f"],
help="Output format: fastmcp (FastMCP-specific) or mcp (MCP protocol). Required when using -o.",
),
] = None,
output: Annotated[
Path,
Path | None,
cyclopts.Parameter(
name=["--output", "-o"],
help="Output file path for the JSON report (default: server-info.json)",
help="Output file path for the JSON report. If not specified, outputs to stdout when format is provided.",
),
] = Path("server-info.json"),
] = None,
python: Annotated[
str | None,
cyclopts.Parameter(
@ -631,13 +644,13 @@ async def inspect(
),
] = None,
with_packages: Annotated[
list[str],
list[str] | None,
cyclopts.Parameter(
"--with",
help="Additional packages to install (can be used multiple times)",
negative="",
),
] = [],
] = None,
project: Annotated[
Path | None,
cyclopts.Parameter(
@ -652,29 +665,39 @@ async def inspect(
help="Requirements file to install dependencies from",
),
] = None,
skip_env: Annotated[
bool,
cyclopts.Parameter(
"--skip-env",
help="Skip environment configuration (for internal use when already in a uv environment)",
negative="",
),
] = False,
) -> None:
"""Inspect an MCP server and generate a JSON report.
"""Inspect an MCP server and display information or generate a JSON report.
This command analyzes an MCP server and generates a comprehensive JSON report
containing information about the server's name, instructions, version, tools,
prompts, resources, templates, and capabilities.
This command analyzes an MCP server. Without flags, it displays a text summary.
Use --format to output complete JSON data.
Examples:
# Show text summary
fastmcp inspect server.py
fastmcp inspect server.py -o report.json
fastmcp inspect server.py:mcp -o analysis.json
fastmcp inspect path/to/server.py:app -o /tmp/server-info.json
# Output FastMCP format JSON to stdout
fastmcp inspect server.py --format fastmcp
# Save MCP protocol format to file (format required with -o)
fastmcp inspect server.py --format mcp -o manifest.json
# Inspect from fastmcp.json configuration
fastmcp inspect fastmcp.json
fastmcp inspect # auto-detect fastmcp.json
Args:
server_spec: Python file to inspect, optionally with :object suffix, or fastmcp.json
"""
from pathlib import Path
from fastmcp.utilities.fastmcp_config import FastMCPConfig
from fastmcp.utilities.fastmcp_config.v1.sources.filesystem import FileSystemSource
# Convert None to empty lists for list parameters
with_packages = with_packages or []
config = None
config_path = None
@ -744,14 +767,15 @@ async def inspect(
source = FileSystemSource(path=server_spec)
config = FastMCPConfig(source=source)
# Check if we need to use uv run
needs_uv = python or with_packages or with_requirements or project
if not needs_uv and config and config.environment:
needs_uv = config.environment.needs_uv()
# Check if we need to use uv run (skip if --skip-env is set)
needs_uv = False
if not skip_env:
needs_uv = python or with_packages or with_requirements or project
if not needs_uv and config and config.environment:
needs_uv = config.environment.needs_uv()
if needs_uv:
# Build and run uv command
from fastmcp.utilities.fastmcp_config import Environment
# Create or update environment config
env_config = Environment(
@ -765,9 +789,14 @@ async def inspect(
"fastmcp",
"inspect",
server_spec,
"--output",
str(output),
"--skip-env", # Prevent infinite loop when calling through uv
]
# Add format and output flags if specified
if format:
inspect_command.extend(["--format", format.value])
if output:
inspect_command.extend(["--output", str(output)])
env_config.run_with_uv(inspect_command)
return # run_with_uv exits the process
@ -775,7 +804,8 @@ async def inspect(
"Inspecting server",
extra={
"server_spec": server_spec,
"output": str(output),
"format": format,
"output": str(output) if output else None,
},
)
@ -783,29 +813,76 @@ async def inspect(
# Load the server using the config
server = await config.source.load_server()
# Get server information - using native async support
# Get basic server information
info = await inspect_fastmcp(server)
info_json = TypeAdapter(FastMCPInfo).dump_json(info, indent=2)
# Check for invalid combination
if output and not format:
console.print(
"[bold red]Error:[/bold red] --format is required when using -o/--output"
)
console.print(
"[dim]Use --format fastmcp or --format mcp to specify the output format[/dim]"
)
sys.exit(1)
# Ensure output directory exists
output.parent.mkdir(parents=True, exist_ok=True)
# If no format specified, show text summary
if format is None:
# Display text summary
console.print()
# Write JSON report (always pretty-printed)
with output.open("w", encoding="utf-8") as f:
f.write(info_json.decode("utf-8"))
# Server section
console.print("[bold]Server[/bold]")
console.print(f" Name: {info.name}")
if info.version:
console.print(f" Version: {info.version}")
console.print(f" Generation: {info.server_generation}")
if info.instructions:
console.print(f" Instructions: {info.instructions}")
console.print()
logger.info(f"Server inspection complete. Report saved to {output}")
# Components section
console.print("[bold]Components[/bold]")
console.print(f" Tools: {len(info.tools)}")
console.print(f" Prompts: {len(info.prompts)}")
console.print(f" Resources: {len(info.resources)}")
console.print(f" Templates: {len(info.templates)}")
console.print()
# Print summary to console
console.print(
f"[bold green]✓[/bold green] Inspected server: [bold]{info.name}[/bold]"
)
console.print(f" Tools: {len(info.tools)}")
console.print(f" Prompts: {len(info.prompts)}")
console.print(f" Resources: {len(info.resources)}")
console.print(f" Templates: {len(info.templates)}")
console.print(f" Report saved to: [cyan]{output}[/cyan]")
# Environment section
console.print("[bold]Environment[/bold]")
console.print(f" FastMCP: {info.fastmcp_version}")
console.print(f" MCP: {info.mcp_version}")
console.print()
console.print(
"[dim]Use --format \\[fastmcp|mcp] for complete JSON output[/dim]"
)
return
# Generate formatted JSON output
formatted_json = await format_info(server, format, info)
# Output to file or stdout
if output:
# Ensure output directory exists
output.parent.mkdir(parents=True, exist_ok=True)
# Write JSON report
with output.open("wb") as f:
f.write(formatted_json)
logger.info(f"Server inspection complete. Report saved to {output}")
# Print confirmation to console
console.print(
f"[bold green]✓[/bold green] Server inspection saved to: [cyan]{output}[/cyan]"
)
console.print(f" Server: [bold]{info.name}[/bold]")
console.print(f" Format: {format.value}")
else:
# Output JSON to stdout
console.print(formatted_json.decode("utf-8"))
except Exception as e:
logger.error(

View file

@ -75,7 +75,7 @@ def install_claude_code(
server_object: str | None,
name: str,
*,
with_editable: Path | None = None,
with_editable: list[Path] | None = None,
with_packages: list[str] | None = None,
env_vars: dict[str, str] | None = None,
python_version: str | None = None,
@ -88,7 +88,7 @@ def install_claude_code(
file: Path to the server file
server_object: Optional server object name (for :object suffix)
name: Name for the server in Claude Code
with_editable: Optional directory to install in editable mode
with_editable: Optional list of directories to install in editable mode
with_packages: Optional list of additional packages to install
env_vars: Optional dictionary of environment variables
python_version: Optional Python version to use
@ -121,7 +121,7 @@ def install_claude_code(
dependencies=deduplicated_packages,
requirements=str(with_requirements) if with_requirements else None,
project=str(project) if project else None,
editable=[str(with_editable)] if with_editable else None,
editable=[str(p) for p in with_editable] if with_editable else None,
)
args = env_config.build_uv_args()
@ -171,28 +171,29 @@ async def claude_code_command(
),
] = None,
with_editable: Annotated[
Path | None,
list[Path] | None,
cyclopts.Parameter(
name=["--with-editable", "-e"],
help="Directory with pyproject.toml to install in editable mode",
"--with-editable",
help="Directory with pyproject.toml to install in editable mode (can be used multiple times)",
negative="",
),
] = None,
with_packages: Annotated[
list[str],
list[str] | None,
cyclopts.Parameter(
"--with",
help="Additional packages to install",
help="Additional packages to install (can be used multiple times)",
negative="",
),
] = [],
] = None,
env_vars: Annotated[
list[str],
list[str] | None,
cyclopts.Parameter(
"--env",
help="Environment variables in KEY=VALUE format",
help="Environment variables in KEY=VALUE format (can be used multiple times)",
negative="",
),
] = [],
] = None,
env_file: Annotated[
Path | None,
cyclopts.Parameter(
@ -227,6 +228,10 @@ async def claude_code_command(
Args:
server_spec: Python file to install, optionally with :object suffix
"""
# Convert None to empty lists for list parameters
with_editable = with_editable or []
with_packages = with_packages or []
env_vars = env_vars or []
file, server_object, name, packages, env_dict = await process_common_args(
server_spec, server_name, with_packages, env_vars, env_file
)

View file

@ -40,7 +40,7 @@ def install_claude_desktop(
server_object: str | None,
name: str,
*,
with_editable: Path | None = None,
with_editable: list[Path] | None = None,
with_packages: list[str] | None = None,
env_vars: dict[str, str] | None = None,
python_version: str | None = None,
@ -53,7 +53,7 @@ def install_claude_desktop(
file: Path to the server file
server_object: Optional server object name (for :object suffix)
name: Name for the server in Claude's config
with_editable: Optional directory to install in editable mode
with_editable: Optional list of directories to install in editable mode
with_packages: Optional list of additional packages to install
env_vars: Optional dictionary of environment variables
python_version: Optional Python version to use
@ -86,7 +86,7 @@ def install_claude_desktop(
dependencies=deduplicated_packages,
requirements=str(with_requirements) if with_requirements else None,
project=str(project) if project else None,
editable=[str(with_editable)] if with_editable else None,
editable=[str(p) for p in with_editable] if with_editable else None,
)
args = env_config.build_uv_args()
@ -143,28 +143,29 @@ async def claude_desktop_command(
),
] = None,
with_editable: Annotated[
Path | None,
list[Path] | None,
cyclopts.Parameter(
name=["--with-editable", "-e"],
help="Directory with pyproject.toml to install in editable mode",
"--with-editable",
help="Directory with pyproject.toml to install in editable mode (can be used multiple times)",
negative="",
),
] = None,
with_packages: Annotated[
list[str],
list[str] | None,
cyclopts.Parameter(
"--with",
help="Additional packages to install",
help="Additional packages to install (can be used multiple times)",
negative="",
),
] = [],
] = None,
env_vars: Annotated[
list[str],
list[str] | None,
cyclopts.Parameter(
"--env",
help="Environment variables in KEY=VALUE format",
help="Environment variables in KEY=VALUE format (can be used multiple times)",
negative="",
),
] = [],
] = None,
env_file: Annotated[
Path | None,
cyclopts.Parameter(
@ -199,6 +200,10 @@ async def claude_desktop_command(
Args:
server_spec: Python file to install, optionally with :object suffix
"""
# Convert None to empty lists for list parameters
with_editable = with_editable or []
with_packages = with_packages or []
env_vars = env_vars or []
file, server_object, name, with_packages, env_dict = await process_common_args(
server_spec, server_name, with_packages, env_vars, env_file
)

View file

@ -71,7 +71,7 @@ def install_cursor_workspace(
name: str,
workspace_path: Path,
*,
with_editable: Path | None = None,
with_editable: list[Path] | None = None,
with_packages: list[str] | None = None,
env_vars: dict[str, str] | None = None,
python_version: str | None = None,
@ -85,7 +85,7 @@ def install_cursor_workspace(
server_object: Optional server object name (for :object suffix)
name: Name for the server in Cursor
workspace_path: Path to the workspace directory
with_editable: Optional directory to install in editable mode
with_editable: Optional list of directories to install in editable mode
with_packages: Optional list of additional packages to install
env_vars: Optional dictionary of environment variables
python_version: Optional Python version to use
@ -120,7 +120,7 @@ def install_cursor_workspace(
dependencies=deduplicated_packages,
requirements=str(with_requirements.resolve()) if with_requirements else None,
project=str(project.resolve()) if project else None,
editable=[str(with_editable.resolve())] if with_editable else None,
editable=[str(p.resolve()) for p in with_editable] if with_editable else None,
)
args = env_config.build_uv_args()
@ -161,7 +161,7 @@ def install_cursor(
server_object: str | None,
name: str,
*,
with_editable: Path | None = None,
with_editable: list[Path] | None = None,
with_packages: list[str] | None = None,
env_vars: dict[str, str] | None = None,
python_version: str | None = None,
@ -175,7 +175,7 @@ def install_cursor(
file: Path to the server file
server_object: Optional server object name (for :object suffix)
name: Name for the server in Cursor
with_editable: Optional directory to install in editable mode
with_editable: Optional list of directories to install in editable mode
with_packages: Optional list of additional packages to install
env_vars: Optional dictionary of environment variables
python_version: Optional Python version to use
@ -200,7 +200,7 @@ def install_cursor(
dependencies=deduplicated_packages,
requirements=str(with_requirements.resolve()) if with_requirements else None,
project=str(project.resolve()) if project else None,
editable=[str(with_editable.resolve())] if with_editable else None,
editable=[str(p.resolve()) for p in with_editable] if with_editable else None,
)
args = env_config.build_uv_args()
@ -262,28 +262,29 @@ async def cursor_command(
),
] = None,
with_editable: Annotated[
Path | None,
list[Path] | None,
cyclopts.Parameter(
name=["--with-editable", "-e"],
help="Directory with pyproject.toml to install in editable mode",
"--with-editable",
help="Directory with pyproject.toml to install in editable mode (can be used multiple times)",
negative="",
),
] = None,
with_packages: Annotated[
list[str],
list[str] | None,
cyclopts.Parameter(
"--with",
help="Additional packages to install",
help="Additional packages to install (can be used multiple times)",
negative="",
),
] = [],
] = None,
env_vars: Annotated[
list[str],
list[str] | None,
cyclopts.Parameter(
"--env",
help="Environment variables in KEY=VALUE format",
help="Environment variables in KEY=VALUE format (can be used multiple times)",
negative="",
),
] = [],
] = None,
env_file: Annotated[
Path | None,
cyclopts.Parameter(
@ -325,6 +326,10 @@ async def cursor_command(
Args:
server_spec: Python file to install, optionally with :object suffix
"""
# Convert None to empty lists for list parameters
with_editable = with_editable or []
with_packages = with_packages or []
env_vars = env_vars or []
file, server_object, name, with_packages, env_dict = await process_common_args(
server_spec, server_name, with_packages, env_vars, env_file
)

View file

@ -22,7 +22,7 @@ def install_mcp_json(
server_object: str | None,
name: str,
*,
with_editable: Path | None = None,
with_editable: list[Path] | None = None,
with_packages: list[str] | None = None,
env_vars: dict[str, str] | None = None,
copy: bool = False,
@ -36,7 +36,7 @@ def install_mcp_json(
file: Path to the server file
server_object: Optional server object name (for :object suffix)
name: Name for the server in MCP config
with_editable: Optional directory to install in editable mode
with_editable: Optional list of directories to install in editable mode
with_packages: Optional list of additional packages to install
env_vars: Optional dictionary of environment variables
copy: If True, copy to clipboard instead of printing to stdout
@ -61,7 +61,7 @@ def install_mcp_json(
dependencies=deduplicated_packages,
requirements=str(with_requirements) if with_requirements else None,
project=str(project) if project else None,
editable=[str(with_editable)] if with_editable else None,
editable=[str(p) for p in with_editable] if with_editable else None,
)
args = env_config.build_uv_args()
@ -116,28 +116,29 @@ async def mcp_json_command(
),
] = None,
with_editable: Annotated[
Path | None,
list[Path] | None,
cyclopts.Parameter(
name=["--with-editable", "-e"],
help="Directory with pyproject.toml to install in editable mode",
"--with-editable",
help="Directory with pyproject.toml to install in editable mode (can be used multiple times)",
negative="",
),
] = None,
with_packages: Annotated[
list[str],
list[str] | None,
cyclopts.Parameter(
"--with",
help="Additional packages to install",
help="Additional packages to install (can be used multiple times)",
negative="",
),
] = [],
] = None,
env_vars: Annotated[
list[str],
list[str] | None,
cyclopts.Parameter(
"--env",
help="Environment variables in KEY=VALUE format",
help="Environment variables in KEY=VALUE format (can be used multiple times)",
negative="",
),
] = [],
] = None,
env_file: Annotated[
Path | None,
cyclopts.Parameter(
@ -180,6 +181,10 @@ async def mcp_json_command(
Args:
server_spec: Python file to install, optionally with :object suffix
"""
# Convert None to empty lists for list parameters
with_editable = with_editable or []
with_packages = with_packages or []
env_vars = env_vars or []
file, server_object, name, packages, env_dict = await process_common_args(
server_spec, server_name, with_packages, env_vars, env_file
)

View file

@ -29,14 +29,17 @@ def parse_env_var(env_var: str) -> tuple[str, str]:
async def process_common_args(
server_spec: str,
server_name: str | None,
with_packages: list[str],
env_vars: list[str],
with_packages: list[str] | None,
env_vars: list[str] | None,
env_file: Path | None,
) -> tuple[Path, str | None, str, list[str], dict[str, str] | None]:
"""Process common arguments shared by all install commands.
Handles both fastmcp.json config files and traditional file.py:object syntax.
"""
# Convert None to empty lists for list parameters
with_packages = with_packages or []
env_vars = env_vars or []
# Create FastMCPConfig from server_spec
config = None
if server_spec.endswith(".json"):

View file

@ -7,12 +7,13 @@ using the OAuth Proxy pattern for non-DCR OAuth flows.
from __future__ import annotations
import httpx
from pydantic import SecretStr
from pydantic import SecretStr, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from fastmcp.server.auth import AccessToken, TokenVerifier
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.server.auth.registry import register_provider
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import NotSet, NotSetT
@ -36,6 +37,11 @@ class AzureProviderSettings(BaseSettings):
required_scopes: list[str] | None = None
timeout_seconds: int | None = None
@field_validator("required_scopes", mode="before")
@classmethod
def _parse_scopes(cls, v):
return parse_scopes(v)
class AzureTokenVerifier(TokenVerifier):
"""Token verifier for Azure OAuth tokens.

View file

@ -22,13 +22,14 @@ Example:
from __future__ import annotations
import httpx
from pydantic import AnyHttpUrl, SecretStr
from pydantic import AnyHttpUrl, SecretStr, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.auth import AccessToken
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.server.auth.registry import register_provider
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import NotSet, NotSetT
@ -51,6 +52,11 @@ class GitHubProviderSettings(BaseSettings):
required_scopes: list[str] | None = None
timeout_seconds: int | None = None
@field_validator("required_scopes", mode="before")
@classmethod
def _parse_scopes(cls, v):
return parse_scopes(v)
class GitHubTokenVerifier(TokenVerifier):
"""Token verifier for GitHub OAuth tokens.

View file

@ -24,13 +24,14 @@ from __future__ import annotations
import time
import httpx
from pydantic import AnyHttpUrl, SecretStr
from pydantic import AnyHttpUrl, SecretStr, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from fastmcp.server.auth import TokenVerifier
from fastmcp.server.auth.auth import AccessToken
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.server.auth.registry import register_provider
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import NotSet, NotSetT
@ -53,6 +54,11 @@ class GoogleProviderSettings(BaseSettings):
required_scopes: list[str] | None = None
timeout_seconds: int | None = None
@field_validator("required_scopes", mode="before")
@classmethod
def _parse_scopes(cls, v):
return parse_scopes(v)
class GoogleTokenVerifier(TokenVerifier):
"""Token verifier for Google OAuth tokens.

View file

@ -11,12 +11,13 @@ from authlib.jose import JsonWebKey, JsonWebToken
from authlib.jose.errors import JoseError
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from pydantic import AnyHttpUrl, SecretStr
from pydantic import AnyHttpUrl, SecretStr, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from typing_extensions import TypedDict
from fastmcp.server.auth import AccessToken, TokenVerifier
from fastmcp.server.auth.registry import register_provider
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import NotSet, NotSetT
@ -155,6 +156,11 @@ class JWTVerifierSettings(BaseSettings):
required_scopes: list[str] | None = None
resource_server_url: AnyHttpUrl | str | None = None
@field_validator("required_scopes", mode="before")
@classmethod
def _parse_scopes(cls, v):
return parse_scopes(v)
@register_provider("JWT")
class JWTVerifier(TokenVerifier):

View file

@ -11,7 +11,7 @@ Choose based on your WorkOS setup and authentication requirements.
from __future__ import annotations
import httpx
from pydantic import AnyHttpUrl, SecretStr
from pydantic import AnyHttpUrl, SecretStr, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from starlette.responses import JSONResponse
from starlette.routing import Route
@ -20,6 +20,7 @@ from fastmcp.server.auth import AccessToken, RemoteAuthProvider, TokenVerifier
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.server.auth.registry import register_provider
from fastmcp.utilities.auth import parse_scopes
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import NotSet, NotSetT
@ -43,6 +44,11 @@ class WorkOSProviderSettings(BaseSettings):
required_scopes: list[str] | None = None
timeout_seconds: int | None = None
@field_validator("required_scopes", mode="before")
@classmethod
def _parse_scopes(cls, v):
return parse_scopes(v)
class WorkOSTokenVerifier(TokenVerifier):
"""Token verifier for WorkOS OAuth tokens.
@ -255,6 +261,11 @@ class AuthKitProviderSettings(BaseSettings):
base_url: AnyHttpUrl
required_scopes: list[str] | None = None
@field_validator("required_scopes", mode="before")
@classmethod
def _parse_scopes(cls, v):
return parse_scopes(v)
@register_provider("AUTHKIT")
class AuthKitProvider(RemoteAuthProvider):

View file

@ -0,0 +1,34 @@
"""Authentication utility helpers."""
from __future__ import annotations
import json
from typing import Any
def parse_scopes(value: Any) -> list[str] | None:
"""Parse scopes from environment variables or settings values.
Accepts either a JSON array string, a comma- or space-separated string,
a list of strings, or ``None``. Returns a list of scopes or ``None`` if
no value is provided.
"""
if value is None or value == "":
return None if value is None else []
if isinstance(value, list):
return [str(v).strip() for v in value if str(v).strip()]
if isinstance(value, str):
value = value.strip()
if not value:
return []
# Try JSON array first
if value.startswith("["):
try:
data = json.loads(value)
if isinstance(data, list):
return [str(v).strip() for v in data if str(v).strip()]
except Exception:
pass
# Fallback to comma/space separated list
return [s.strip() for s in value.replace(",", " ").split() if s.strip()]
return value

View file

@ -4,8 +4,10 @@ from __future__ import annotations
import importlib.metadata
from dataclasses import dataclass
from typing import Any, cast
from enum import Enum
from typing import Any, Literal, cast
import pydantic_core
from mcp.server.fastmcp import FastMCP as FastMCP1x
import fastmcp
@ -21,9 +23,12 @@ class ToolInfo:
name: str
description: str | None
input_schema: dict[str, Any]
output_schema: dict[str, Any] | None = None
annotations: dict[str, Any] | None = None
tags: list[str] | None = None
enabled: bool | None = None
title: str | None = None
meta: dict[str, Any] | None = None
@dataclass
@ -36,6 +41,8 @@ class PromptInfo:
arguments: list[dict[str, Any]] | None = None
tags: list[str] | None = None
enabled: bool | None = None
title: str | None = None
meta: dict[str, Any] | None = None
@dataclass
@ -47,8 +54,11 @@ class ResourceInfo:
name: str | None
description: str | None
mime_type: str | None = None
annotations: dict[str, Any] | None = None
tags: list[str] | None = None
enabled: bool | None = None
title: str | None = None
meta: dict[str, Any] | None = None
@dataclass
@ -60,8 +70,12 @@ class TemplateInfo:
name: str | None
description: str | None
mime_type: str | None = None
parameters: dict[str, Any] | None = None
annotations: dict[str, Any] | None = None
tags: list[str] | None = None
enabled: bool | None = None
title: str | None = None
meta: dict[str, Any] | None = None
@dataclass
@ -70,9 +84,10 @@ class FastMCPInfo:
name: str
instructions: str | None
fastmcp_version: str
mcp_version: str
server_version: str | None
version: str | None # The server's own version string (if specified)
fastmcp_version: str # Version of FastMCP generating this manifest
mcp_version: str # Version of MCP protocol library
server_generation: int # Server generation: 1 (mcp package) or 2 (fastmcp)
tools: list[ToolInfo]
prompts: list[PromptInfo]
resources: list[ResourceInfo]
@ -106,9 +121,12 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
name=tool.name or key,
description=tool.description,
input_schema=mcp_tool.inputSchema if mcp_tool.inputSchema else {},
output_schema=tool.output_schema,
annotations=tool.annotations.model_dump() if tool.annotations else None,
tags=list(tool.tags) if tool.tags else None,
enabled=tool.enabled,
title=tool.title,
meta=tool.meta,
)
)
@ -125,6 +143,8 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
else None,
tags=list(prompt.tags) if prompt.tags else None,
enabled=prompt.enabled,
title=prompt.title,
meta=prompt.meta,
)
)
@ -138,8 +158,13 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
name=resource.name,
description=resource.description,
mime_type=resource.mime_type,
annotations=resource.annotations.model_dump()
if resource.annotations
else None,
tags=list(resource.tags) if resource.tags else None,
enabled=resource.enabled,
title=resource.title,
meta=resource.meta,
)
)
@ -153,8 +178,14 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
name=template.name,
description=template.description,
mime_type=template.mime_type,
parameters=template.parameters,
annotations=template.annotations.model_dump()
if template.annotations
else None,
tags=list(template.tags) if template.tags else None,
enabled=template.enabled,
title=template.title,
meta=template.meta,
)
)
@ -171,9 +202,8 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
instructions=mcp.instructions,
fastmcp_version=fastmcp.__version__,
mcp_version=importlib.metadata.version("mcp"),
server_version=(
mcp.version if hasattr(mcp, "version") else mcp._mcp_server.version
),
server_generation=2, # FastMCP v2
version=(mcp.version if hasattr(mcp, "version") else mcp._mcp_server.version),
tools=tool_infos,
prompts=prompt_infos,
resources=resource_infos,
@ -191,7 +221,6 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
Returns:
FastMCPInfo dataclass containing the extracted information
"""
# Use a client to interact with the FastMCP1x server
async with Client(mcp) as client:
# Get components via client calls (these return MCP objects)
@ -208,25 +237,18 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
# Extract detailed tool information from MCP Tool objects
tool_infos = []
for mcp_tool in mcp_tools:
# Extract annotations if they exist
annotations = None
if hasattr(mcp_tool, "annotations") and mcp_tool.annotations:
if hasattr(mcp_tool.annotations, "model_dump"):
annotations = mcp_tool.annotations.model_dump()
elif isinstance(mcp_tool.annotations, dict):
annotations = mcp_tool.annotations
else:
annotations = None
tool_infos.append(
ToolInfo(
key=mcp_tool.name, # For 1.x, key and name are the same
key=mcp_tool.name,
name=mcp_tool.name,
description=mcp_tool.description,
input_schema=mcp_tool.inputSchema if mcp_tool.inputSchema else {},
annotations=annotations,
tags=None, # 1.x doesn't have tags
enabled=None, # 1.x doesn't have enabled field
output_schema=None, # v1 doesn't have output_schema
annotations=None, # v1 doesn't have annotations
tags=None, # v1 doesn't have tags
enabled=None, # v1 doesn't have enabled field
title=None, # v1 doesn't have title
meta=None, # v1 doesn't have meta field
)
)
@ -240,12 +262,14 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
prompt_infos.append(
PromptInfo(
key=mcp_prompt.name, # For 1.x, key and name are the same
key=mcp_prompt.name,
name=mcp_prompt.name,
description=mcp_prompt.description,
arguments=arguments,
tags=None, # 1.x doesn't have tags
enabled=None, # 1.x doesn't have enabled field
tags=None, # v1 doesn't have tags
enabled=None, # v1 doesn't have enabled field
title=None, # v1 doesn't have title
meta=None, # v1 doesn't have meta field
)
)
@ -254,13 +278,16 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
for mcp_resource in mcp_resources:
resource_infos.append(
ResourceInfo(
key=str(mcp_resource.uri), # For 1.x, key and uri are the same
key=str(mcp_resource.uri),
uri=str(mcp_resource.uri),
name=mcp_resource.name,
description=mcp_resource.description,
mime_type=mcp_resource.mimeType,
tags=None, # 1.x doesn't have tags
enabled=None, # 1.x doesn't have enabled field
annotations=None, # v1 doesn't have annotations
tags=None, # v1 doesn't have tags
enabled=None, # v1 doesn't have enabled field
title=None, # v1 doesn't have title
meta=None, # v1 doesn't have meta field
)
)
@ -269,15 +296,17 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
for mcp_template in mcp_templates:
template_infos.append(
TemplateInfo(
key=str(
mcp_template.uriTemplate
), # For 1.x, key and uriTemplate are the same
key=str(mcp_template.uriTemplate),
uri_template=str(mcp_template.uriTemplate),
name=mcp_template.name,
description=mcp_template.description,
mime_type=mcp_template.mimeType,
tags=None, # 1.x doesn't have tags
enabled=None, # 1.x doesn't have enabled field
parameters=None, # v1 doesn't expose template parameters
annotations=None, # v1 doesn't have annotations
tags=None, # v1 doesn't have tags
enabled=None, # v1 doesn't have enabled field
title=None, # v1 doesn't have title
meta=None, # v1 doesn't have meta field
)
)
@ -292,13 +321,14 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
return FastMCPInfo(
name=mcp._mcp_server.name,
instructions=mcp._mcp_server.instructions,
fastmcp_version=importlib.metadata.version("mcp"),
fastmcp_version=fastmcp.__version__, # Version generating this manifest
mcp_version=importlib.metadata.version("mcp"),
server_version=mcp._mcp_server.version,
server_generation=1, # MCP v1
version=mcp._mcp_server.version,
tools=tool_infos,
prompts=prompt_infos,
resources=resource_infos,
templates=template_infos, # FastMCP1x does have templates
templates=template_infos,
capabilities=capabilities,
)
@ -319,3 +349,102 @@ async def inspect_fastmcp(mcp: FastMCP[Any] | FastMCP1x) -> FastMCPInfo:
return await inspect_fastmcp_v1(mcp)
else:
return await inspect_fastmcp_v2(cast(FastMCP[Any], mcp))
class InspectFormat(str, Enum):
"""Output format for inspect command."""
FASTMCP = "fastmcp"
MCP = "mcp"
async def format_fastmcp_info(info: FastMCPInfo) -> bytes:
"""Format FastMCPInfo as FastMCP-specific JSON.
This includes FastMCP-specific fields like tags, enabled, annotations, etc.
"""
# Build the output dict with nested structure
result = {
"server": {
"name": info.name,
"instructions": info.instructions,
"version": info.version,
"generation": info.server_generation,
"capabilities": info.capabilities,
},
"environment": {
"fastmcp": info.fastmcp_version,
"mcp": info.mcp_version,
},
"tools": info.tools,
"prompts": info.prompts,
"resources": info.resources,
"templates": info.templates,
}
return pydantic_core.to_json(result, indent=2)
async def format_mcp_info(mcp: FastMCP[Any] | FastMCP1x) -> bytes:
"""Format server info as standard MCP protocol JSON.
Uses Client to get the standard MCP protocol format with camelCase fields.
Includes version metadata at the top level.
"""
async with Client(mcp) as client:
# Get all the MCP protocol objects
tools_result = await client.list_tools_mcp()
prompts_result = await client.list_prompts_mcp()
resources_result = await client.list_resources_mcp()
templates_result = await client.list_resource_templates_mcp()
# Get server info from the initialize result
server_info = client.initialize_result.serverInfo
# Combine into MCP protocol structure with environment metadata
result = {
"environment": {
"fastmcp": fastmcp.__version__, # Version generating this manifest
"mcp": importlib.metadata.version("mcp"), # MCP protocol version
},
"serverInfo": server_info,
"capabilities": {}, # MCP format doesn't include capabilities at top level
"tools": tools_result.tools,
"prompts": prompts_result.prompts,
"resources": resources_result.resources,
"resourceTemplates": templates_result.resourceTemplates,
}
return pydantic_core.to_json(result, indent=2)
async def format_info(
mcp: FastMCP[Any] | FastMCP1x,
format: InspectFormat | Literal["fastmcp", "mcp"],
info: FastMCPInfo | None = None,
) -> bytes:
"""Format server information according to the specified format.
Args:
mcp: The FastMCP instance
format: Output format ("fastmcp" or "mcp")
info: Pre-extracted FastMCPInfo (optional, will be extracted if not provided)
Returns:
JSON bytes in the requested format
"""
# Convert string to enum if needed
if isinstance(format, str):
format = InspectFormat(format)
if format == InspectFormat.MCP:
# MCP format doesn't need FastMCPInfo, it uses Client directly
return await format_mcp_info(mcp)
elif format == InspectFormat.FASTMCP:
# For FastMCP format, we need the FastMCPInfo
# This works for both v1 and v2 servers
if info is None:
info = await inspect_fastmcp(mcp)
return await format_fastmcp_info(info)
else:
raise ValueError(f"Unknown format: {format}")

View file

@ -509,6 +509,42 @@ class TestInspectCommand:
# Output is parsed as a Path object
assert bound.arguments["output"] == output_file
async def test_inspect_command_text_summary(self, tmp_path, capsys):
"""Test inspect command with no format shows text summary."""
# Create a real server file
server_file = tmp_path / "test_server.py"
server_file.write_text("""
import fastmcp
mcp = fastmcp.FastMCP("InspectTestServer", instructions="Test instructions", version="1.0.0")
@mcp.tool
def test_tool(x: int) -> int:
return x * 2
""")
# Parse and execute the command without format or output
command, bound, _ = app.parse_args(
[
"inspect",
str(server_file),
]
)
await command(**bound.arguments)
# Check the console output
captured = capsys.readouterr()
# Check for the table format output
assert "InspectTestServer" in captured.out
assert "Test instructions" in captured.out
assert "1.0.0" in captured.out
assert "Tools" in captured.out
assert "1" in captured.out # number of tools
assert "FastMCP" in captured.out
assert "MCP" in captured.out
assert "Use --format [fastmcp|mcp] for complete JSON output" in captured.out
async def test_inspect_command_with_real_server(self, tmp_path):
"""Test inspect command with a real server file."""
# Create a real server file
@ -529,11 +565,13 @@ def test_prompt(name: str) -> str:
output_file = tmp_path / "inspect_output.json"
# Parse and execute the command
# Parse and execute the command with format and output file
command, bound, _ = app.parse_args(
[
"inspect",
str(server_file),
"--format",
"fastmcp",
"--output",
str(output_file),
]
@ -545,7 +583,10 @@ def test_prompt(name: str) -> str:
assert output_file.exists()
content = output_file.read_text()
# Basic checks that the inspection worked
assert "InspectTestServer" in content
assert "test_tool" in content
assert "test_prompt" in content
# Basic checks that the fastmcp format worked
import json
data = json.loads(content)
assert data["server"]["name"] == "InspectTestServer"
assert len(data["tools"]) == 1
assert len(data["prompts"]) == 1

View file

@ -246,7 +246,7 @@ class TestInstallCursor:
file=Path("/path/to/server.py"),
server_object="custom_app",
name="test-server",
with_editable=editable_path,
with_editable=[editable_path],
)
assert result is True
@ -328,7 +328,7 @@ class TestCursorCommand:
file=Path("server.py"),
server_object=None,
name="test-server",
with_editable=None,
with_editable=[],
with_packages=[],
env_vars={},
python_version=None,

View file

@ -31,7 +31,14 @@ class TestAzureProvider:
parsed_token = urlparse(provider._upstream_token_endpoint)
assert "87654321-4321-4321-4321-210987654321" in parsed_token.path
def test_init_with_env_vars(self):
@pytest.mark.parametrize(
"scopes_env",
[
"User.Read,Calendar.Read",
'["User.Read", "Calendar.Read"]',
],
)
def test_init_with_env_vars(self, scopes_env):
"""Test AzureProvider initialization from environment variables."""
with patch.dict(
os.environ,
@ -40,7 +47,7 @@ class TestAzureProvider:
"FASTMCP_SERVER_AUTH_AZURE_CLIENT_SECRET": "env-secret",
"FASTMCP_SERVER_AUTH_AZURE_TENANT_ID": "env-tenant-id",
"FASTMCP_SERVER_AUTH_AZURE_BASE_URL": "https://envserver.com",
"FASTMCP_SERVER_AUTH_AZURE_REQUIRED_SCOPES": '["User.Read", "Calendar.Read"]',
"FASTMCP_SERVER_AUTH_AZURE_REQUIRED_SCOPES": scopes_env,
},
):
provider = AzureProvider()
@ -48,6 +55,10 @@ class TestAzureProvider:
assert provider._upstream_client_id == "env-client-id"
assert provider._upstream_client_secret.get_secret_value() == "env-secret"
assert str(provider.base_url) == "https://envserver.com/"
assert provider._token_validator.required_scopes == [
"User.Read",
"Calendar.Read",
]
# Check tenant is in the endpoints
parsed_auth = urlparse(provider._upstream_authorization_endpoint)
assert "env-tenant-id" in parsed_auth.path

View file

@ -83,7 +83,14 @@ class TestGitHubProvider:
) # URLs get normalized with trailing slash
assert provider._redirect_path == "/custom/callback"
def test_init_with_env_vars(self):
@pytest.mark.parametrize(
"scopes_env",
[
"user,repo",
'["user", "repo"]',
],
)
def test_init_with_env_vars(self, scopes_env):
"""Test initialization with environment variables."""
with patch.dict(
os.environ,
@ -91,6 +98,7 @@ class TestGitHubProvider:
"FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID": "env_client_id",
"FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET": "env_secret",
"FASTMCP_SERVER_AUTH_GITHUB_BASE_URL": "https://env-example.com",
"FASTMCP_SERVER_AUTH_GITHUB_REQUIRED_SCOPES": scopes_env,
},
):
provider = GitHubProvider()
@ -98,6 +106,7 @@ class TestGitHubProvider:
assert provider._upstream_client_id == "env_client_id"
assert provider._upstream_client_secret.get_secret_value() == "env_secret"
assert str(provider.base_url) == "https://env-example.com/"
assert provider._token_validator.required_scopes == ["user", "repo"]
def test_init_explicit_overrides_env(self):
"""Test that explicit parameters override environment variables."""

View file

@ -24,7 +24,14 @@ class TestGoogleProvider:
assert provider._upstream_client_secret.get_secret_value() == "GOCSPX-test123"
assert str(provider.base_url) == "https://myserver.com/"
def test_init_with_env_vars(self):
@pytest.mark.parametrize(
"scopes_env",
[
"openid,https://www.googleapis.com/auth/userinfo.email",
'["openid", "https://www.googleapis.com/auth/userinfo.email"]',
],
)
def test_init_with_env_vars(self, scopes_env):
"""Test GoogleProvider initialization from environment variables."""
with patch.dict(
os.environ,
@ -32,7 +39,7 @@ class TestGoogleProvider:
"FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_ID": "env123.apps.googleusercontent.com",
"FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_SECRET": "GOCSPX-env456",
"FASTMCP_SERVER_AUTH_GOOGLE_BASE_URL": "https://envserver.com",
"FASTMCP_SERVER_AUTH_GOOGLE_REQUIRED_SCOPES": '["openid", "https://www.googleapis.com/auth/userinfo.email"]',
"FASTMCP_SERVER_AUTH_GOOGLE_REQUIRED_SCOPES": scopes_env,
},
):
provider = GoogleProvider()
@ -42,6 +49,10 @@ class TestGoogleProvider:
provider._upstream_client_secret.get_secret_value() == "GOCSPX-env456"
)
assert str(provider.base_url) == "https://envserver.com/"
assert provider._token_validator.required_scopes == [
"openid",
"https://www.googleapis.com/auth/userinfo.email",
]
def test_init_missing_client_id_raises_error(self):
"""Test that missing client_id raises ValueError."""

View file

@ -26,7 +26,14 @@ class TestWorkOSProvider:
assert provider._upstream_client_secret.get_secret_value() == "secret_test456"
assert str(provider.base_url) == "https://myserver.com/"
def test_init_with_env_vars(self):
@pytest.mark.parametrize(
"scopes_env",
[
"openid,email",
'["openid", "email"]',
],
)
def test_init_with_env_vars(self, scopes_env):
"""Test WorkOSProvider initialization from environment variables."""
with patch.dict(
os.environ,
@ -35,7 +42,7 @@ class TestWorkOSProvider:
"FASTMCP_SERVER_AUTH_WORKOS_CLIENT_SECRET": "env_secret",
"FASTMCP_SERVER_AUTH_WORKOS_AUTHKIT_DOMAIN": "https://env.authkit.app",
"FASTMCP_SERVER_AUTH_WORKOS_BASE_URL": "https://envserver.com",
"FASTMCP_SERVER_AUTH_WORKOS_REQUIRED_SCOPES": '["openid", "email"]',
"FASTMCP_SERVER_AUTH_WORKOS_REQUIRED_SCOPES": scopes_env,
},
):
provider = WorkOSProvider()
@ -43,6 +50,10 @@ class TestWorkOSProvider:
assert provider._upstream_client_id == "env_client"
assert provider._upstream_client_secret.get_secret_value() == "env_secret"
assert str(provider.base_url) == "https://envserver.com/"
assert provider._token_validator.required_scopes == [
"openid",
"email",
]
def test_init_missing_client_id_raises_error(self):
"""Test that missing client_id raises ValueError."""

View file

@ -8,7 +8,11 @@ import fastmcp
from fastmcp import Client, FastMCP
from fastmcp.utilities.inspect import (
FastMCPInfo,
InspectFormat,
ToolInfo,
format_fastmcp_info,
format_info,
format_mcp_info,
inspect_fastmcp,
inspect_fastmcp_v1,
)
@ -20,14 +24,22 @@ class TestFastMCPInfo:
def test_fastmcp_info_creation(self):
"""Test that FastMCPInfo can be created with all required fields."""
tool = ToolInfo(
key="tool1", name="tool1", description="Test tool", input_schema={}
key="tool1",
name="tool1",
description="Test tool",
input_schema={},
output_schema={
"type": "object",
"properties": {"result": {"type": "string"}},
},
)
info = FastMCPInfo(
name="TestServer",
instructions="Test instructions",
fastmcp_version="1.0.0",
mcp_version="1.0.0",
server_version="1.0.0",
server_generation=2,
version="1.0.0",
tools=[tool],
prompts=[],
resources=[],
@ -39,7 +51,8 @@ class TestFastMCPInfo:
assert info.instructions == "Test instructions"
assert info.fastmcp_version == "1.0.0"
assert info.mcp_version == "1.0.0"
assert info.server_version == "1.0.0"
assert info.server_generation == 2
assert info.version == "1.0.0"
assert len(info.tools) == 1
assert info.tools[0].name == "tool1"
assert info.capabilities == {"tools": {"listChanged": True}}
@ -51,7 +64,8 @@ class TestFastMCPInfo:
instructions=None,
fastmcp_version="1.0.0",
mcp_version="1.0.0",
server_version="1.0.0",
server_generation=2,
version="1.0.0",
tools=[],
prompts=[],
resources=[],
@ -75,7 +89,8 @@ class TestGetFastMCPInfo:
assert info.instructions is None
assert info.fastmcp_version == fastmcp.__version__
assert info.mcp_version == importlib.metadata.version("mcp")
assert info.server_version is None
assert info.server_generation == 2 # v2 server
assert info.version is None
assert info.tools == []
assert info.prompts == []
assert info.resources == []
@ -95,7 +110,7 @@ class TestGetFastMCPInfo:
"""Test get_fastmcp_info with a server that has a version."""
mcp = FastMCP("VersionServer", version="1.2.3")
info = await inspect_fastmcp(mcp)
assert info.server_version == "1.2.3"
assert info.version == "1.2.3"
async def test_server_with_tools(self):
"""Test get_fastmcp_info with a server that has tools."""
@ -266,9 +281,10 @@ class TestFastMCP1xCompatibility:
assert info.name == "Test1x"
assert info.instructions is None
assert info.fastmcp_version == importlib.metadata.version("mcp")
assert info.fastmcp_version == fastmcp.__version__ # CLI version
assert info.mcp_version == importlib.metadata.version("mcp")
assert info.server_version is None
assert info.server_generation == 1 # v1 server
assert info.version is None
assert info.tools == []
assert info.prompts == []
assert info.resources == []
@ -310,6 +326,7 @@ class TestFastMCP1xCompatibility:
resource_uris = [res.uri for res in info.resources]
assert "resource://data" in resource_uris
assert len(info.templates) == 0 # No templates added in this test
assert info.server_generation == 1 # v1 server
async def test_fastmcp1x_with_prompts(self):
"""Test get_fastmcp_info_v1 with a FastMCP1x server that has prompts."""
@ -341,6 +358,7 @@ class TestFastMCP1xCompatibility:
tool_names = [tool.name for tool in info.tools]
assert "test_tool" in tool_names
assert len(info.templates) == 0 # No templates added in this test
assert info.server_generation == 1 # v1 server
async def test_dispatcher_with_fastmcp2x(self):
"""Test that the main get_fastmcp_info function correctly dispatches to v2."""
@ -384,9 +402,191 @@ class TestFastMCP1xCompatibility:
assert "tool2x" in tool2x_names
# Check server versions
assert info1x.server_version is None
assert info2x.server_version is None
assert info1x.server_generation == 1 # v1
assert info2x.server_generation == 2 # v2
assert info1x.version is None
assert info2x.version is None
# No templates added in these tests
assert len(info1x.templates) == 0
assert len(info2x.templates) == 0
class TestFormatFunctions:
"""Tests for the formatting functions."""
async def test_format_fastmcp_info(self):
"""Test formatting as FastMCP-specific JSON."""
mcp = FastMCP("TestServer", instructions="Test instructions", version="1.2.3")
@mcp.tool
def test_tool(x: int) -> dict:
"""A test tool."""
return {"result": x * 2}
info = await inspect_fastmcp(mcp)
json_bytes = await format_fastmcp_info(info)
# Verify it's valid JSON
import json
data = json.loads(json_bytes)
# Check FastMCP-specific fields are present
assert "server" in data
assert data["server"]["name"] == "TestServer"
assert data["server"]["instructions"] == "Test instructions"
assert data["server"]["generation"] == 2 # v2 server
assert data["server"]["version"] == "1.2.3"
assert "capabilities" in data["server"]
# Check environment information
assert "environment" in data
assert data["environment"]["fastmcp"] == fastmcp.__version__
assert data["environment"]["mcp"] == importlib.metadata.version("mcp")
# Check tools
assert len(data["tools"]) == 1
assert data["tools"][0]["name"] == "test_tool"
assert data["tools"][0]["enabled"] is True
assert "tags" in data["tools"][0]
async def test_format_mcp_info(self):
"""Test formatting as MCP protocol JSON."""
mcp = FastMCP("TestServer", instructions="Test instructions", version="2.0.0")
@mcp.tool
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
@mcp.prompt
def test_prompt(name: str) -> list:
"""Test prompt."""
return [{"role": "user", "content": f"Hello {name}"}]
json_bytes = await format_mcp_info(mcp)
# Verify it's valid JSON
import json
data = json.loads(json_bytes)
# Check MCP protocol structure with camelCase
assert "serverInfo" in data
assert data["serverInfo"]["name"] == "TestServer"
# Check server version in MCP format
assert data["serverInfo"]["version"] == "2.0.0"
# MCP format SHOULD have environment fields
assert "environment" in data
assert data["environment"]["fastmcp"] == fastmcp.__version__
assert data["environment"]["mcp"] == importlib.metadata.version("mcp")
assert "capabilities" in data
assert "tools" in data
assert "prompts" in data
assert "resources" in data
assert "resourceTemplates" in data
# Check tools have MCP format (camelCase fields)
assert len(data["tools"]) == 1
assert data["tools"][0]["name"] == "add"
assert "inputSchema" in data["tools"][0]
# FastMCP-specific fields should not be present
assert "tags" not in data["tools"][0]
assert "enabled" not in data["tools"][0]
async def test_format_info_with_fastmcp_format(self):
"""Test format_info with fastmcp format."""
mcp = FastMCP("TestServer")
@mcp.tool
def test() -> str:
return "test"
# Test with string format
json_bytes = await format_info(mcp, "fastmcp")
import json
data = json.loads(json_bytes)
assert data["server"]["name"] == "TestServer"
assert "tags" in data["tools"][0] # FastMCP-specific field
# Test with enum format
json_bytes = await format_info(mcp, InspectFormat.FASTMCP)
data = json.loads(json_bytes)
assert data["server"]["name"] == "TestServer"
async def test_format_info_with_mcp_format(self):
"""Test format_info with mcp format."""
mcp = FastMCP("TestServer")
@mcp.tool
def test() -> str:
return "test"
json_bytes = await format_info(mcp, "mcp")
import json
data = json.loads(json_bytes)
assert "serverInfo" in data
assert "tools" in data
assert "inputSchema" in data["tools"][0] # MCP uses camelCase
async def test_format_info_requires_format(self):
"""Test that format_info requires a format parameter."""
mcp = FastMCP("TestServer")
@mcp.tool
def test() -> str:
return "test"
# Should work with valid formats
json_bytes = await format_info(mcp, "fastmcp")
assert json_bytes
json_bytes = await format_info(mcp, "mcp")
assert json_bytes
# Should fail with invalid format
import pytest
with pytest.raises(ValueError, match="not a valid InspectFormat"):
await format_info(mcp, "invalid") # type: ignore
async def test_tool_with_output_schema(self):
"""Test that output_schema is properly extracted and included."""
mcp = FastMCP("TestServer")
@mcp.tool(
output_schema={
"type": "object",
"properties": {
"result": {"type": "number"},
"message": {"type": "string"},
},
}
)
def compute(x: int) -> dict:
"""Compute something."""
return {"result": x * 2, "message": f"Doubled {x}"}
info = await inspect_fastmcp(mcp)
# Check output_schema is captured
assert len(info.tools) == 1
assert info.tools[0].output_schema is not None
assert info.tools[0].output_schema["type"] == "object"
assert "result" in info.tools[0].output_schema["properties"]
# Verify it's included in FastMCP format
json_bytes = await format_fastmcp_info(info)
import json
data = json.loads(json_bytes)
# Tools are at the top level, not nested
assert data["tools"][0]["output_schema"]["type"] == "object"