mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-20 04:24:17 +02:00
Merge branch 'main' into claude/issue-1625-20250826-0138
This commit is contained in:
commit
36760418c2
53 changed files with 1505 additions and 336 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 <config> --project <output-dir>
|
||||
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
```
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
25
docs/python-sdk/fastmcp-utilities-auth.mdx
Normal file
25
docs/python-sdk/fastmcp-utilities-auth.mdx
Normal 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.
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: fastmcp_config
|
||||
sidebarTitle: fastmcp_config
|
||||
title: __init__
|
||||
sidebarTitle: __init__
|
||||
---
|
||||
|
||||
# `fastmcp.utilities.fastmcp_config`
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
title: __init__
|
||||
sidebarTitle: __init__
|
||||
---
|
||||
|
||||
# `fastmcp.utilities.fastmcp_config.v1`
|
||||
|
||||
*This module is empty or contains only private/internal implementations.*
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
title: __init__
|
||||
sidebarTitle: __init__
|
||||
---
|
||||
|
||||
# `fastmcp.utilities.fastmcp_config.v1.sources`
|
||||
|
||||
*This module is empty or contains only private/internal implementations.*
|
||||
|
|
@ -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.
|
||||
|
||||
|
|
@ -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.
|
||||
|
||||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"):
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
34
src/fastmcp/utilities/auth.py
Normal file
34
src/fastmcp/utilities/auth.py
Normal 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
|
||||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue