Support factory functions in fastmcp run (#1384)

This commit is contained in:
Jeremiah Lowin 2025-08-06 13:12:52 -04:00 committed by GitHub
commit d52d6d8fff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 249 additions and 143 deletions

View file

@ -18,15 +18,13 @@ fastmcp --help
| Command | Purpose | Dependency Management |
| ------- | ------- | --------------------- |
| `run` | Run a FastMCP server directly | Default: Uses your local environment directly. With `--python`, `--with`, `--project`, or `--with-requirements`: Runs via `uv run` subprocess |
| `dev` | Run a server with the MCP Inspector for testing | Always runs via `uv run` subprocess (never uses your local environment); dependencies must be specified or available in a uv-managed project |
| `install` | Install a server in MCP client applications | Creates an isolated environment; dependencies must be explicitly specified with `--with` and/or `--with-editable` |
| `inspect` | Generate a JSON report about a FastMCP server | Uses your current environment; you are responsible for ensuring all dependencies are available |
| `run` | Run a FastMCP server directly | **Supports:** Local files, factory functions, URLs, MCP configs. **Deps:** Uses your local environment directly. With `--python`, `--with`, `--project`, or `--with-requirements`: Runs via `uv run` subprocess |
| `dev` | Run a server with the MCP Inspector for testing | **Supports:** Local files only. **Deps:** Always runs via `uv run` subprocess (never uses your local environment); dependencies must be specified or available in a uv-managed project |
| `install` | Install a server in MCP client applications | **Supports:** Local files only. **Deps:** Creates an isolated environment; dependencies must be explicitly specified with `--with` and/or `--with-editable` |
| `inspect` | Generate a JSON report about a FastMCP server | **Supports:** Local files only. **Deps:** Uses your current environment; you are responsible for ensuring all dependencies are available |
| `version` | Display version information | N/A |
## Command Details
### `run`
## `fastmcp run`
Run a FastMCP server directly or proxy a remote server.
@ -38,7 +36,7 @@ fastmcp run server.py
By default, this command runs the server directly in your current Python environment. You are responsible for ensuring all dependencies are available. When using `--python`, `--with`, `--project`, or `--with-requirements` options, it runs the server via `uv run` subprocess instead.
</Tip>
#### Options
### Options
| Option | Flag | Description |
| ------ | ---- | ----------- |
@ -54,106 +52,125 @@ By default, this command runs the server directly in your current Python environ
| Requirements File | `--with-requirements` | Requirements file to install dependencies from |
#### Server Specification
### Entrypoints
<VersionBadge version="2.3.5" />
The server can be specified in four ways:
1. `server.py` - imports the module and looks for a FastMCP object named `mcp`, `server`, or `app`. Errors if no such object is found.
2. `server.py:custom_name` - imports and uses the specified server object
3. `http://server-url/path` or `https://server-url/path` - connects to a remote server and creates a proxy
4. `mcp.json` - runs servers defined in a standard MCP configuration file
The `fastmcp run` command supports the following entrypoints:
<Tip>
When using `fastmcp run` with a local file, it **ignores** the `if __name__ == "__main__"` block entirely. Instead, it finds your server object and calls its `run()` method directly with the transport options you specify. This means you can use `fastmcp run` to override the transport specified in your code.
</Tip>
1. **[Inferred server instance](#inferred-server-instance)**: `server.py` - imports the module and looks for a FastMCP object named `mcp`, `server`, or `app`. Errors if no such object is found.
2. **[Explicit server object](#explicit-server-object)**: `server.py:custom_name` - imports and uses the specified server object
3. **[Factory function](#factory-function)**: `server.py:create_server` - calls the specified function (sync or async) to create a server instance
4. **[Remote server proxy](#remote-server-proxy)**: `https://example.com/mcp-server` - connects to a remote server and creates a **local proxy server**
5. **MCP configuration file**: `mcp.json` - runs servers defined in a standard MCP configuration file
For example, if your code contains:
<Warning>
Note: When using `fastmcp run` with a local file, it **completely ignores** the `if __name__ == "__main__"` block. This means:
- Any setup code in `__main__` will NOT run
- Server configuration in `__main__` is bypassed
- `fastmcp run` finds your server object/factory and runs it with its own transport settings
```python
# server.py
If you need setup code to run, use the **factory pattern** instead.
</Warning>
#### Inferred Server Instance
If you provide a path to a file, `fastmcp run` will load the file and look for a FastMCP server instance stored as a variable named `mcp`, `server`, or `app`. If no such object is found, it will raise an error.
For example, if you have a file called `server.py` with the following content:
```python server.py
from fastmcp import FastMCP
mcp = FastMCP("MyServer")
@mcp.tool
def hello(name: str) -> str:
return f"Hello, {name}!"
if __name__ == "__main__":
# This is ignored when using `fastmcp run`!
mcp.run(transport="stdio")
```
You can run it with Streamable HTTP transport regardless of what's in the `__main__` block:
You can run it with:
```bash
fastmcp run server.py --transport http --port 8000
fastmcp run server.py
```
**Examples**
#### Explicit Server Object
If your server is stored as a variable with a custom name, or you want to be explicit about which server to run, you can use the following syntax to load a specific server object:
```bash
# Run a local server with Streamable HTTP transport on a custom port
fastmcp run server.py --transport http --port 8000
# Connect to a remote server and proxy as a stdio server
fastmcp run https://example.com/mcp-server
# Connect to a remote server with specified log level
fastmcp run https://example.com/mcp-server --log-level DEBUG
# Run with a specific Python version
fastmcp run server.py --python 3.11
# Run with additional packages
fastmcp run server.py --with pandas --with numpy
# Run within a specific project directory
fastmcp run server.py --project /path/to/project
# Run with dependencies from a requirements file
fastmcp run server.py --with-requirements requirements.txt
fastmcp run server.py:custom_name
```
#### Running MCP Configuration Files
For example, if you have a file called `server.py` with the following content:
FastMCP can run servers defined in standard MCP configuration files (typically named `mcp.json`). When you run an mcp.json file, FastMCP creates a proxy server that runs all the servers referenced in the configuration.
```python
from fastmcp import FastMCP
**Example mcp.json:**
```json
{
"mcpServers": {
"fetch": {
"command": "uvx",
"args": [
"mcp-server-fetch"
]
},
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/Users/username/Documents"
]
}
}
}
my_server = FastMCP("CustomServer")
@my_server.tool
def hello() -> str:
return "Hello from custom server!"
```
**Run the configuration:**
You can run it with:
```bash
fastmcp run server.py:custom_name
```
#### Factory Function
<VersionBadge version="2.11.2" />
Since `fastmcp run` ignores the `if __name__ == "__main__"` block, you can use a factory function to run setup code before your server starts. Factory functions are called without any arguments and must return a FastMCP server instance. Both sync and async factory functions are supported.
The syntax for using a factory function is the same as for an explicit server object: `fastmcp run server.py:factory_fn`. FastMCP will automatically detect that you have identified a function rather than a server Instance
For example, if you have a file called `server.py` with the following content:
```python
from fastmcp import FastMCP
async def create_server() -> FastMCP:
mcp = FastMCP("MyServer")
@mcp.tool
def add(x: int, y: int) -> int:
return x + y
# Setup that runs with fastmcp run
tool = await mcp.get_tool("add")
tool.disable()
return mcp
```
You can run it with:
```bash
fastmcp run server.py:create_server
```
#### Remote Server Proxy
FastMCP run can also start a local proxy server that connects to a remote server. This is useful when you want to run a remote server locally for testing or development purposes, or to use with a client that doesn't support direct connections to remote servers.
To start a local proxy, you can use the following syntax:
```bash
fastmcp run https://example.com/mcp
```
#### MCP Configuration
FastMCP can also run servers defined in a standard MCP configuration file. This is useful when you want to run multiple servers from a single file, or when you want to use a client that doesn't support direct connections to remote servers.
To run a MCP configuration file, you can use the following syntax:
```bash
# Run with default stdio transport
fastmcp run mcp.json
# Run with HTTP transport on custom port
fastmcp run mcp.json --transport http --port 8080
# Run with SSE transport
fastmcp run mcp.json --transport sse
```
### `dev`
This will run all the servers defined in the file.
## `fastmcp dev`
Run a MCP server with the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) for testing.
@ -182,7 +199,7 @@ This command does not support HTTP testing. To test a server over Streamable HTT
2. Open the MCP Inspector separately and connect to your running server
</Warning>
#### Options
### Options
| Option | Flag | Description |
| ------ | ---- | ----------- |
@ -195,6 +212,18 @@ This command does not support HTTP testing. To test a server over Streamable HTT
| Project Directory | `--project` | Run the command within the given project directory |
| Requirements File | `--with-requirements` | Requirements file to install dependencies from |
### Entrypoints
The `dev` command supports local FastMCP server files only:
1. **Inferred server instance**: `server.py` - imports the module and looks for a FastMCP object named `mcp`, `server`, or `app`. Errors if no such object is found.
2. **Explicit server object**: `server.py:custom_name` - imports and uses the specified server object
3. **Factory function**: `server.py:create_server` - calls the specified function (sync or async) to create a server instance
<Warning>
The `dev` command **only supports local files** - no URLs, remote servers, or MCP configuration files.
</Warning>
**Examples**
```bash
@ -211,7 +240,7 @@ fastmcp dev server.py --with-requirements requirements.txt
fastmcp dev server.py --project /path/to/project
```
### `install`
## `fastmcp install`
<VersionBadge version="2.10.3" />
Install a MCP server in MCP client applications. FastMCP currently supports the following clients:
@ -242,14 +271,7 @@ Note that for security reasons, MCP clients usually run every server in a comple
**FastMCP `install` commands focus on local server files with STDIO transport.** For remote servers running with HTTP or SSE transport, use your client's native configuration - FastMCP's value is simplifying the complex local setup with dependencies and `uv` commands.
</Tip>
#### Server Specification
The `install` command supports the same `file.py:object` notation as the `run` command:
1. `server.py` - imports the module and looks for a FastMCP object named `mcp`, `server`, or `app`. Errors if no such object is found.
2. `server.py:custom_name` - imports and uses the specified server object
#### Options
### Options
| Option | Flag | Description |
| ------ | ---- | ----------- |
@ -262,6 +284,22 @@ The `install` command supports the same `file.py:object` notation as the `run` c
| Project Directory | `--project` | Run the command within the given project directory |
| Requirements File | `--with-requirements` | Requirements file to install dependencies from |
### Entrypoints
The `install` command supports local FastMCP server files only:
1. **Inferred server instance**: `server.py` - imports the module and looks for a FastMCP object named `mcp`, `server`, or `app`. Errors if no such object is found.
2. **Explicit server object**: `server.py:custom_name` - imports and uses the specified server object
3. **Factory function**: `server.py:create_server` - calls the specified function (sync or async) to create a server instance
<Note>
Factory functions are particularly useful for install commands since they allow setup code to run that would otherwise be ignored when the MCP client runs your server.
</Note>
<Warning>
The `install` command **only supports local files** - no URLs, remote servers, or MCP configuration files. For remote servers, use your MCP client's native configuration.
</Warning>
**Examples**
```bash
@ -299,7 +337,7 @@ fastmcp install mcp-json server.py --name "My Server" --with pandas
fastmcp install mcp-json server.py --copy
```
#### MCP JSON Generation
### MCP JSON Generation
The `mcp-json` subcommand generates standard MCP JSON configuration that can be used with any MCP-compatible client. This is useful when:
@ -339,7 +377,7 @@ To use this configuration with your MCP client, you'll typically need to add it
| ------ | ---- | ----------- |
| Copy to Clipboard | `--copy` | Copy configuration to clipboard instead of printing to stdout |
### `inspect`
## `fastmcp inspect`
<VersionBadge version="2.9.0" />
@ -349,7 +387,25 @@ Generate a detailed JSON report about a FastMCP server, including information ab
fastmcp inspect server.py
```
The command supports the same server specification format as `run` and `install`:
### Options
| Option | Flag | Description |
| ------ | ---- | ----------- |
| Output File | `--output`, `-o` | Output file path for the JSON report (default: server-info.json) |
### Entrypoints
The `inspect` command supports local FastMCP server files only:
1. **Inferred server instance**: `server.py` - imports the module and looks for a FastMCP object named `mcp`, `server`, or `app`. Errors if no such object is found.
2. **Explicit server object**: `server.py:custom_name` - imports and uses the specified server object
3. **Factory function**: `server.py:create_server` - calls the specified function (sync or async) to create a server instance
<Warning>
The `inspect` command **only supports local files** - no URLs, remote servers, or MCP configuration files.
</Warning>
**Examples**
```bash
# Auto-detect server object
@ -362,7 +418,7 @@ fastmcp inspect server.py:my_server
fastmcp inspect server.py --output analysis.json
```
### `version`
## `fastmcp version`
Display version information about FastMCP and related components.
@ -370,7 +426,7 @@ Display version information about FastMCP and related components.
fastmcp version
```
#### Options
### Options
| Option | Flag | Description |
| ------ | ---- | ----------- |

View file

@ -138,7 +138,7 @@ def version(
@app.command
def dev(
async def dev(
server_spec: str,
*,
with_editable: Annotated[
@ -220,7 +220,7 @@ def dev(
try:
# Import server to get dependencies
server: FastMCP = run_module.import_server(file, server_object)
server: FastMCP = await run_module.import_server(file, server_object)
if server.dependencies is not None:
with_packages = list(set(with_packages + server.dependencies))
@ -283,7 +283,7 @@ def dev(
@app.command
def run(
async def run(
server_spec: str,
*server_args: str,
transport: Annotated[
@ -414,7 +414,7 @@ def run(
else:
# Use direct import for backwards compatibility
try:
run_module.run_command(
await run_module.run_command(
server_spec=server_spec,
transport=transport,
host=host,
@ -476,7 +476,7 @@ async def inspect(
try:
# Import the server
server = run_module.import_server(file, server_object)
server = await run_module.import_server(file, server_object)
# Get server information - using native async support
info = await inspect_fastmcp(server)

View file

@ -167,7 +167,7 @@ def install_claude_code(
return False
def claude_code_command(
async def claude_code_command(
server_spec: str,
*,
server_name: Annotated[
@ -234,7 +234,7 @@ def claude_code_command(
Args:
server_spec: Python file to install, optionally with :object suffix
"""
file, server_object, name, packages, env_dict = process_common_args(
file, server_object, name, packages, env_dict = await process_common_args(
server_spec, server_name, with_packages, env_vars, env_file
)

View file

@ -140,7 +140,7 @@ def install_claude_desktop(
return False
def claude_desktop_command(
async def claude_desktop_command(
server_spec: str,
*,
server_name: Annotated[
@ -207,7 +207,7 @@ def claude_desktop_command(
Args:
server_spec: Python file to install, optionally with :object suffix
"""
file, server_object, name, with_packages, env_dict = process_common_args(
file, server_object, name, with_packages, env_dict = await process_common_args(
server_spec, server_name, with_packages, env_vars, env_file
)

View file

@ -150,7 +150,7 @@ def install_cursor(
return False
def cursor_command(
async def cursor_command(
server_spec: str,
*,
server_name: Annotated[
@ -217,7 +217,7 @@ def cursor_command(
Args:
server_spec: Python file to install, optionally with :object suffix
"""
file, server_object, name, with_packages, env_dict = process_common_args(
file, server_object, name, with_packages, env_dict = await process_common_args(
server_spec, server_name, with_packages, env_vars, env_file
)

View file

@ -113,7 +113,7 @@ def install_mcp_json(
return False
def mcp_json_command(
async def mcp_json_command(
server_spec: str,
*,
server_name: Annotated[
@ -188,7 +188,7 @@ def mcp_json_command(
Args:
server_spec: Python file to install, optionally with :object suffix
"""
file, server_object, name, packages, env_dict = process_common_args(
file, server_object, name, packages, env_dict = await process_common_args(
server_spec, server_name, with_packages, env_vars, env_file
)

View file

@ -23,7 +23,7 @@ def parse_env_var(env_var: str) -> tuple[str, str]:
return key.strip(), value.strip()
def process_common_args(
async def process_common_args(
server_spec: str,
server_name: str | None,
with_packages: list[str],
@ -49,7 +49,7 @@ def process_common_args(
server = None
if not name:
try:
server = import_server(file, server_object)
server = await import_server(file, server_object)
name = server.name
except (ImportError, ModuleNotFoundError) as e:
logger.debug(

View file

@ -1,6 +1,7 @@
"""FastMCP run command implementation with enhanced type hints."""
import importlib.util
import inspect
import json
import re
import subprocess
@ -58,15 +59,15 @@ def parse_file_path(server_spec: str) -> tuple[Path, str | None]:
return file_path, server_object
def import_server(file: Path, server_object: str | None = None) -> Any:
async def 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_object: Optional object name in format "module:object" or just "object"
server_or_factory: Optional object name in format "module:object" or just "object"
Returns:
The server object
The server object (or result of calling a factory function)
"""
# Add parent directory to Python path so imports can be resolved
file_dir = str(file.parent)
@ -86,11 +87,12 @@ def import_server(file: Path, server_object: str | None = None) -> Any:
spec.loader.exec_module(module)
# If no object specified, try common server names
if not server_object:
# Look for the most common server object names
if not server_or_factory:
# Look for common server instance names
for name in ["mcp", "server", "app"]:
if hasattr(module, name):
return getattr(module, name)
obj = getattr(module, name)
return await _resolve_server_or_factory(obj, file, name)
logger.error(
f"No server object found in {file}. Please either:\n"
@ -100,14 +102,14 @@ def import_server(file: Path, server_object: str | None = None) -> Any:
)
sys.exit(1)
assert server_object is not None
assert server_or_factory is not None
# Handle module:object syntax
if ":" in server_object:
module_name, object_name = server_object.split(":", 1)
if ":" in server_or_factory:
module_name, object_name = server_or_factory.split(":", 1)
try:
server_module = importlib.import_module(module_name)
server = getattr(server_module, object_name, None)
obj = getattr(server_module, object_name, None)
except ImportError:
logger.error(
f"Could not import module '{module_name}'",
@ -116,16 +118,62 @@ def import_server(file: Path, server_object: str | None = None) -> Any:
sys.exit(1)
else:
# Just object name
server = getattr(module, server_object, None)
obj = getattr(module, server_or_factory, None)
if server is None:
if obj is None:
logger.error(
f"Server object '{server_object}' not found",
f"Server object '{server_or_factory}' not found",
extra={"file": str(file)},
)
sys.exit(1)
return server
return await _resolve_server_or_factory(obj, file, server_or_factory)
async def _resolve_server_or_factory(obj: Any, file: Path, name: str) -> Any:
"""Resolve a server object or factory function to a server instance.
Args:
obj: The object that might be a server or factory function
file: Path to the file for error messages
name: Name of the object for error messages
Returns:
A server instance
"""
# Check if it's a function or coroutine function
if inspect.isfunction(obj) or inspect.iscoroutinefunction(obj):
logger.debug(f"Found factory function '{name}' in {file}")
try:
if inspect.iscoroutinefunction(obj):
# Async factory function
server = await obj()
else:
# Sync factory function
server = obj()
# Validate the result is a FastMCP server
if not isinstance(server, FastMCP | FastMCP1x):
logger.error(
f"Factory function '{name}' must return a FastMCP server instance, "
f"got {type(server).__name__}",
extra={"file": str(file)},
)
sys.exit(1)
logger.debug(f"Factory function '{name}' created server: {server.name}")
return server
except Exception as e:
logger.error(
f"Failed to call factory function '{name}': {e}",
extra={"file": str(file)},
)
sys.exit(1)
# Not a function, return as-is (should be a server instance)
return obj
def run_with_uv(
@ -219,7 +267,7 @@ def create_client_server(url: str) -> Any:
import fastmcp
client = fastmcp.Client(url)
server = fastmcp.FastMCP.from_client(client)
server = fastmcp.FastMCP.as_proxy(client)
return server
except Exception as e:
logger.error(f"Failed to create client for URL {url}: {e}")
@ -237,14 +285,16 @@ def create_mcp_config_server(mcp_config_path: Path) -> FastMCP[None]:
return server
def import_server_with_args(
file: Path, server_object: str | None = None, server_args: list[str] | None = None
async def 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_object: Optional server object name
server_or_factory: Optional server object or factory function name
server_args: Optional command line arguments to inject
Returns:
@ -254,14 +304,14 @@ def import_server_with_args(
original_argv = sys.argv[:]
try:
sys.argv = [str(file)] + server_args
return import_server(file, server_object)
return await import_server(file, server_or_factory)
finally:
sys.argv = original_argv
else:
return import_server(file, server_object)
return await import_server(file, server_or_factory)
def run_command(
async def run_command(
server_spec: str,
transport: TransportType | None = None,
host: str | None = None,
@ -293,8 +343,8 @@ def run_command(
server = create_mcp_config_server(Path(server_spec))
else:
# Handle file case
file, server_object = parse_file_path(server_spec)
server = import_server_with_args(file, server_object, server_args)
file, server_or_factory = parse_file_path(server_spec)
server = await import_server_with_args(file, server_or_factory, server_args)
logger.debug(f'Found server "{server.name}" in {file}')
# Run the server
@ -320,7 +370,7 @@ def run_command(
kwargs["show_banner"] = False
try:
server.run(**kwargs)
await server.run_async(**kwargs)
except Exception as e:
logger.error(f"Failed to run server: {e}")
sys.exit(1)

View file

@ -306,7 +306,7 @@ class TestCursorCommand:
@patch("fastmcp.cli.install.cursor.install_cursor")
@patch("fastmcp.cli.install.cursor.process_common_args")
def test_cursor_command_basic(self, mock_process_args, mock_install):
async def test_cursor_command_basic(self, mock_process_args, mock_install):
"""Test basic cursor command execution."""
mock_process_args.return_value = (
Path("server.py"),
@ -318,7 +318,7 @@ class TestCursorCommand:
mock_install.return_value = True
with patch("sys.exit") as mock_exit:
cursor_command("server.py")
await cursor_command("server.py")
mock_install.assert_called_once_with(
file=Path("server.py"),
@ -335,7 +335,7 @@ class TestCursorCommand:
@patch("fastmcp.cli.install.cursor.install_cursor")
@patch("fastmcp.cli.install.cursor.process_common_args")
def test_cursor_command_failure(self, mock_process_args, mock_install):
async def test_cursor_command_failure(self, mock_process_args, mock_install):
"""Test cursor command when installation fails."""
mock_process_args.return_value = (
Path("server.py"),
@ -347,6 +347,6 @@ class TestCursorCommand:
mock_install.return_value = False
with pytest.raises(SystemExit) as exc_info:
cursor_command("server.py")
await cursor_command("server.py")
assert exc_info.value.code == 1

View file

@ -157,7 +157,7 @@ def greet(name: str) -> str:
return f"Hello, {name}!"
""")
server = import_server(test_file)
server = await import_server(test_file)
assert server.name == "TestServer"
tools = await server.get_tools()
assert "greet" in tools
@ -178,12 +178,12 @@ if __name__ == "__main__":
app.run()
""")
server = import_server(test_file)
server = await import_server(test_file)
assert server.name == "MainServer"
tools = await server.get_tools()
assert "calculate" in tools
def test_import_server_standard_names(self, tmp_path):
async def test_import_server_standard_names(self, tmp_path):
"""Test automatic detection of standard names (mcp, server, app)."""
# Test with 'mcp' name
mcp_file = tmp_path / "mcp_server.py"
@ -192,7 +192,7 @@ import fastmcp
mcp = fastmcp.FastMCP("MCPServer")
""")
server = import_server(mcp_file)
server = await import_server(mcp_file)
assert server.name == "MCPServer"
# Test with 'server' name
@ -202,7 +202,7 @@ import fastmcp
server = fastmcp.FastMCP("ServerServer")
""")
server = import_server(server_file)
server = await import_server(server_file)
assert server.name == "ServerServer"
# Test with 'app' name
@ -212,7 +212,7 @@ import fastmcp
app = fastmcp.FastMCP("AppServer")
""")
server = import_server(app_file)
server = await import_server(app_file)
assert server.name == "AppServer"
async def test_import_server_nonstandard_name(self, tmp_path):
@ -228,12 +228,12 @@ def custom_tool() -> str:
return "custom"
""")
server = import_server(test_file, "my_custom_server")
server = await import_server(test_file, "my_custom_server")
assert server.name == "CustomServer"
tools = await server.get_tools()
assert "custom_tool" in tools
def test_import_server_no_standard_names_fails(self, tmp_path):
async def test_import_server_no_standard_names_fails(self, tmp_path):
"""Test importing server when no standard names exist fails."""
test_file = tmp_path / "server.py"
test_file.write_text("""
@ -243,10 +243,10 @@ other_name = fastmcp.FastMCP("OtherServer")
""")
with pytest.raises(SystemExit) as exc_info:
import_server(test_file)
await import_server(test_file)
assert exc_info.value.code == 1
def test_import_server_nonexistent_object_fails(self, tmp_path):
async def test_import_server_nonexistent_object_fails(self, tmp_path):
"""Test importing nonexistent server object fails."""
test_file = tmp_path / "server.py"
test_file.write_text("""
@ -256,5 +256,5 @@ mcp = fastmcp.FastMCP("TestServer")
""")
with pytest.raises(SystemExit) as exc_info:
import_server(test_file, "nonexistent")
await import_server(test_file, "nonexistent")
assert exc_info.value.code == 1