Spaces:
Running
Running
Jeremiah Lowin commited on
Support factory functions in fastmcp run (#1384)
Browse files- docs/patterns/cli.mdx +145 -89
- src/fastmcp/cli/cli.py +5 -5
- src/fastmcp/cli/install/claude_code.py +2 -2
- src/fastmcp/cli/install/claude_desktop.py +2 -2
- src/fastmcp/cli/install/cursor.py +2 -2
- src/fastmcp/cli/install/mcp_json.py +2 -2
- src/fastmcp/cli/install/shared.py +2 -2
- src/fastmcp/cli/run.py +74 -24
- tests/cli/test_cursor.py +4 -4
- tests/cli/test_run.py +11 -11
docs/patterns/cli.mdx
CHANGED
|
@@ -18,15 +18,13 @@ fastmcp --help
|
|
| 18 |
|
| 19 |
| Command | Purpose | Dependency Management |
|
| 20 |
| ------- | ------- | --------------------- |
|
| 21 |
-
| `run` | Run a FastMCP server directly |
|
| 22 |
-
| `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 |
|
| 23 |
-
| `install` | Install a server in MCP client applications | Creates an isolated environment; dependencies must be explicitly specified with `--with` and/or `--with-editable` |
|
| 24 |
-
| `inspect` | Generate a JSON report about a FastMCP server | Uses your current environment; you are responsible for ensuring all dependencies are available |
|
| 25 |
| `version` | Display version information | N/A |
|
| 26 |
|
| 27 |
-
##
|
| 28 |
-
|
| 29 |
-
### `run`
|
| 30 |
|
| 31 |
Run a FastMCP server directly or proxy a remote server.
|
| 32 |
|
|
@@ -38,7 +36,7 @@ fastmcp run server.py
|
|
| 38 |
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.
|
| 39 |
</Tip>
|
| 40 |
|
| 41 |
-
###
|
| 42 |
|
| 43 |
| Option | Flag | Description |
|
| 44 |
| ------ | ---- | ----------- |
|
|
@@ -54,106 +52,125 @@ By default, this command runs the server directly in your current Python environ
|
|
| 54 |
| Requirements File | `--with-requirements` | Requirements file to install dependencies from |
|
| 55 |
|
| 56 |
|
| 57 |
-
###
|
| 58 |
<VersionBadge version="2.3.5" />
|
| 59 |
|
| 60 |
-
The
|
| 61 |
-
1. `server.py` - imports the module and looks for a FastMCP object named `mcp`, `server`, or `app`. Errors if no such object is found.
|
| 62 |
-
2. `server.py:custom_name` - imports and uses the specified server object
|
| 63 |
-
3. `http://server-url/path` or `https://server-url/path` - connects to a remote server and creates a proxy
|
| 64 |
-
4. `mcp.json` - runs servers defined in a standard MCP configuration file
|
| 65 |
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
|
|
|
|
|
|
| 69 |
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
-
|
| 73 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
from fastmcp import FastMCP
|
| 75 |
|
| 76 |
mcp = FastMCP("MyServer")
|
|
|
|
| 77 |
|
| 78 |
-
|
| 79 |
-
def hello(name: str) -> str:
|
| 80 |
-
return f"Hello, {name}!"
|
| 81 |
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
mcp.run(transport="stdio")
|
| 85 |
```
|
| 86 |
|
| 87 |
-
|
|
|
|
|
|
|
| 88 |
|
| 89 |
```bash
|
| 90 |
-
fastmcp run server.py
|
| 91 |
```
|
| 92 |
|
| 93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
|
| 95 |
```bash
|
| 96 |
-
|
| 97 |
-
|
| 98 |
|
| 99 |
-
#
|
| 100 |
-
|
| 101 |
|
| 102 |
-
|
| 103 |
-
fastmcp run https://example.com/mcp-server --log-level DEBUG
|
| 104 |
|
| 105 |
-
|
| 106 |
-
fastmcp run server.py --python 3.11
|
| 107 |
|
| 108 |
-
|
| 109 |
-
fastmcp run server.py --with pandas --with numpy
|
| 110 |
|
| 111 |
-
|
| 112 |
-
fastmcp
|
| 113 |
|
| 114 |
-
|
| 115 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
```
|
| 117 |
|
| 118 |
-
|
| 119 |
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
**Example mcp.json:**
|
| 123 |
-
```json
|
| 124 |
-
{
|
| 125 |
-
"mcpServers": {
|
| 126 |
-
"fetch": {
|
| 127 |
-
"command": "uvx",
|
| 128 |
-
"args": [
|
| 129 |
-
"mcp-server-fetch"
|
| 130 |
-
]
|
| 131 |
-
},
|
| 132 |
-
"filesystem": {
|
| 133 |
-
"command": "npx",
|
| 134 |
-
"args": [
|
| 135 |
-
"-y",
|
| 136 |
-
"@modelcontextprotocol/server-filesystem",
|
| 137 |
-
"/Users/username/Documents"
|
| 138 |
-
]
|
| 139 |
-
}
|
| 140 |
-
}
|
| 141 |
-
}
|
| 142 |
```
|
| 143 |
|
| 144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
```bash
|
| 146 |
-
|
| 147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
|
| 149 |
-
|
| 150 |
-
fastmcp run mcp.json --transport http --port 8080
|
| 151 |
|
| 152 |
-
|
| 153 |
-
fastmcp run mcp.json
|
| 154 |
```
|
| 155 |
|
| 156 |
-
|
|
|
|
|
|
|
| 157 |
|
| 158 |
Run a MCP server with the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) for testing.
|
| 159 |
|
|
@@ -182,7 +199,7 @@ This command does not support HTTP testing. To test a server over Streamable HTT
|
|
| 182 |
2. Open the MCP Inspector separately and connect to your running server
|
| 183 |
</Warning>
|
| 184 |
|
| 185 |
-
###
|
| 186 |
|
| 187 |
| Option | Flag | Description |
|
| 188 |
| ------ | ---- | ----------- |
|
|
@@ -195,6 +212,18 @@ This command does not support HTTP testing. To test a server over Streamable HTT
|
|
| 195 |
| Project Directory | `--project` | Run the command within the given project directory |
|
| 196 |
| Requirements File | `--with-requirements` | Requirements file to install dependencies from |
|
| 197 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
**Examples**
|
| 199 |
|
| 200 |
```bash
|
|
@@ -211,7 +240,7 @@ fastmcp dev server.py --with-requirements requirements.txt
|
|
| 211 |
fastmcp dev server.py --project /path/to/project
|
| 212 |
```
|
| 213 |
|
| 214 |
-
##
|
| 215 |
<VersionBadge version="2.10.3" />
|
| 216 |
|
| 217 |
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
|
|
| 242 |
**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.
|
| 243 |
</Tip>
|
| 244 |
|
| 245 |
-
###
|
| 246 |
-
|
| 247 |
-
The `install` command supports the same `file.py:object` notation as the `run` command:
|
| 248 |
-
|
| 249 |
-
1. `server.py` - imports the module and looks for a FastMCP object named `mcp`, `server`, or `app`. Errors if no such object is found.
|
| 250 |
-
2. `server.py:custom_name` - imports and uses the specified server object
|
| 251 |
-
|
| 252 |
-
#### Options
|
| 253 |
|
| 254 |
| Option | Flag | Description |
|
| 255 |
| ------ | ---- | ----------- |
|
|
@@ -262,6 +284,22 @@ The `install` command supports the same `file.py:object` notation as the `run` c
|
|
| 262 |
| Project Directory | `--project` | Run the command within the given project directory |
|
| 263 |
| Requirements File | `--with-requirements` | Requirements file to install dependencies from |
|
| 264 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
**Examples**
|
| 266 |
|
| 267 |
```bash
|
|
@@ -299,7 +337,7 @@ fastmcp install mcp-json server.py --name "My Server" --with pandas
|
|
| 299 |
fastmcp install mcp-json server.py --copy
|
| 300 |
```
|
| 301 |
|
| 302 |
-
###
|
| 303 |
|
| 304 |
The `mcp-json` subcommand generates standard MCP JSON configuration that can be used with any MCP-compatible client. This is useful when:
|
| 305 |
|
|
@@ -339,7 +377,7 @@ To use this configuration with your MCP client, you'll typically need to add it
|
|
| 339 |
| ------ | ---- | ----------- |
|
| 340 |
| Copy to Clipboard | `--copy` | Copy configuration to clipboard instead of printing to stdout |
|
| 341 |
|
| 342 |
-
##
|
| 343 |
|
| 344 |
<VersionBadge version="2.9.0" />
|
| 345 |
|
|
@@ -349,7 +387,25 @@ Generate a detailed JSON report about a FastMCP server, including information ab
|
|
| 349 |
fastmcp inspect server.py
|
| 350 |
```
|
| 351 |
|
| 352 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 353 |
|
| 354 |
```bash
|
| 355 |
# Auto-detect server object
|
|
@@ -362,7 +418,7 @@ fastmcp inspect server.py:my_server
|
|
| 362 |
fastmcp inspect server.py --output analysis.json
|
| 363 |
```
|
| 364 |
|
| 365 |
-
##
|
| 366 |
|
| 367 |
Display version information about FastMCP and related components.
|
| 368 |
|
|
@@ -370,7 +426,7 @@ Display version information about FastMCP and related components.
|
|
| 370 |
fastmcp version
|
| 371 |
```
|
| 372 |
|
| 373 |
-
###
|
| 374 |
|
| 375 |
| Option | Flag | Description |
|
| 376 |
| ------ | ---- | ----------- |
|
|
|
|
| 18 |
|
| 19 |
| Command | Purpose | Dependency Management |
|
| 20 |
| ------- | ------- | --------------------- |
|
| 21 |
+
| `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 |
|
| 22 |
+
| `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 |
|
| 23 |
+
| `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` |
|
| 24 |
+
| `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 |
|
| 25 |
| `version` | Display version information | N/A |
|
| 26 |
|
| 27 |
+
## `fastmcp run`
|
|
|
|
|
|
|
| 28 |
|
| 29 |
Run a FastMCP server directly or proxy a remote server.
|
| 30 |
|
|
|
|
| 36 |
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.
|
| 37 |
</Tip>
|
| 38 |
|
| 39 |
+
### Options
|
| 40 |
|
| 41 |
| Option | Flag | Description |
|
| 42 |
| ------ | ---- | ----------- |
|
|
|
|
| 52 |
| Requirements File | `--with-requirements` | Requirements file to install dependencies from |
|
| 53 |
|
| 54 |
|
| 55 |
+
### Entrypoints
|
| 56 |
<VersionBadge version="2.3.5" />
|
| 57 |
|
| 58 |
+
The `fastmcp run` command supports the following entrypoints:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
+
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.
|
| 61 |
+
2. **[Explicit server object](#explicit-server-object)**: `server.py:custom_name` - imports and uses the specified server object
|
| 62 |
+
3. **[Factory function](#factory-function)**: `server.py:create_server` - calls the specified function (sync or async) to create a server instance
|
| 63 |
+
4. **[Remote server proxy](#remote-server-proxy)**: `https://example.com/mcp-server` - connects to a remote server and creates a **local proxy server**
|
| 64 |
+
5. **MCP configuration file**: `mcp.json` - runs servers defined in a standard MCP configuration file
|
| 65 |
|
| 66 |
+
<Warning>
|
| 67 |
+
Note: When using `fastmcp run` with a local file, it **completely ignores** the `if __name__ == "__main__"` block. This means:
|
| 68 |
+
- Any setup code in `__main__` will NOT run
|
| 69 |
+
- Server configuration in `__main__` is bypassed
|
| 70 |
+
- `fastmcp run` finds your server object/factory and runs it with its own transport settings
|
| 71 |
|
| 72 |
+
If you need setup code to run, use the **factory pattern** instead.
|
| 73 |
+
</Warning>
|
| 74 |
+
|
| 75 |
+
#### Inferred Server Instance
|
| 76 |
+
|
| 77 |
+
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.
|
| 78 |
+
|
| 79 |
+
For example, if you have a file called `server.py` with the following content:
|
| 80 |
+
|
| 81 |
+
```python server.py
|
| 82 |
from fastmcp import FastMCP
|
| 83 |
|
| 84 |
mcp = FastMCP("MyServer")
|
| 85 |
+
```
|
| 86 |
|
| 87 |
+
You can run it with:
|
|
|
|
|
|
|
| 88 |
|
| 89 |
+
```bash
|
| 90 |
+
fastmcp run server.py
|
|
|
|
| 91 |
```
|
| 92 |
|
| 93 |
+
#### Explicit Server Object
|
| 94 |
+
|
| 95 |
+
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:
|
| 96 |
|
| 97 |
```bash
|
| 98 |
+
fastmcp run server.py:custom_name
|
| 99 |
```
|
| 100 |
|
| 101 |
+
For example, if you have a file called `server.py` with the following content:
|
| 102 |
+
|
| 103 |
+
```python
|
| 104 |
+
from fastmcp import FastMCP
|
| 105 |
+
|
| 106 |
+
my_server = FastMCP("CustomServer")
|
| 107 |
+
|
| 108 |
+
@my_server.tool
|
| 109 |
+
def hello() -> str:
|
| 110 |
+
return "Hello from custom server!"
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
You can run it with:
|
| 114 |
|
| 115 |
```bash
|
| 116 |
+
fastmcp run server.py:custom_name
|
| 117 |
+
```
|
| 118 |
|
| 119 |
+
#### Factory Function
|
| 120 |
+
<VersionBadge version="2.11.2" />
|
| 121 |
|
| 122 |
+
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.
|
|
|
|
| 123 |
|
| 124 |
+
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
|
|
|
|
| 125 |
|
| 126 |
+
For example, if you have a file called `server.py` with the following content:
|
|
|
|
| 127 |
|
| 128 |
+
```python
|
| 129 |
+
from fastmcp import FastMCP
|
| 130 |
|
| 131 |
+
async def create_server() -> FastMCP:
|
| 132 |
+
mcp = FastMCP("MyServer")
|
| 133 |
+
|
| 134 |
+
@mcp.tool
|
| 135 |
+
def add(x: int, y: int) -> int:
|
| 136 |
+
return x + y
|
| 137 |
+
|
| 138 |
+
# Setup that runs with fastmcp run
|
| 139 |
+
tool = await mcp.get_tool("add")
|
| 140 |
+
tool.disable()
|
| 141 |
+
|
| 142 |
+
return mcp
|
| 143 |
```
|
| 144 |
|
| 145 |
+
You can run it with:
|
| 146 |
|
| 147 |
+
```bash
|
| 148 |
+
fastmcp run server.py:create_server
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
```
|
| 150 |
|
| 151 |
+
#### Remote Server Proxy
|
| 152 |
+
|
| 153 |
+
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.
|
| 154 |
+
|
| 155 |
+
To start a local proxy, you can use the following syntax:
|
| 156 |
+
|
| 157 |
```bash
|
| 158 |
+
fastmcp run https://example.com/mcp
|
| 159 |
+
```
|
| 160 |
+
|
| 161 |
+
#### MCP Configuration
|
| 162 |
+
|
| 163 |
+
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.
|
| 164 |
|
| 165 |
+
To run a MCP configuration file, you can use the following syntax:
|
|
|
|
| 166 |
|
| 167 |
+
```bash
|
| 168 |
+
fastmcp run mcp.json
|
| 169 |
```
|
| 170 |
|
| 171 |
+
This will run all the servers defined in the file.
|
| 172 |
+
|
| 173 |
+
## `fastmcp dev`
|
| 174 |
|
| 175 |
Run a MCP server with the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) for testing.
|
| 176 |
|
|
|
|
| 199 |
2. Open the MCP Inspector separately and connect to your running server
|
| 200 |
</Warning>
|
| 201 |
|
| 202 |
+
### Options
|
| 203 |
|
| 204 |
| Option | Flag | Description |
|
| 205 |
| ------ | ---- | ----------- |
|
|
|
|
| 212 |
| Project Directory | `--project` | Run the command within the given project directory |
|
| 213 |
| Requirements File | `--with-requirements` | Requirements file to install dependencies from |
|
| 214 |
|
| 215 |
+
### Entrypoints
|
| 216 |
+
|
| 217 |
+
The `dev` command supports local FastMCP server files only:
|
| 218 |
+
|
| 219 |
+
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.
|
| 220 |
+
2. **Explicit server object**: `server.py:custom_name` - imports and uses the specified server object
|
| 221 |
+
3. **Factory function**: `server.py:create_server` - calls the specified function (sync or async) to create a server instance
|
| 222 |
+
|
| 223 |
+
<Warning>
|
| 224 |
+
The `dev` command **only supports local files** - no URLs, remote servers, or MCP configuration files.
|
| 225 |
+
</Warning>
|
| 226 |
+
|
| 227 |
**Examples**
|
| 228 |
|
| 229 |
```bash
|
|
|
|
| 240 |
fastmcp dev server.py --project /path/to/project
|
| 241 |
```
|
| 242 |
|
| 243 |
+
## `fastmcp install`
|
| 244 |
<VersionBadge version="2.10.3" />
|
| 245 |
|
| 246 |
Install a MCP server in MCP client applications. FastMCP currently supports the following clients:
|
|
|
|
| 271 |
**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.
|
| 272 |
</Tip>
|
| 273 |
|
| 274 |
+
### Options
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
|
| 276 |
| Option | Flag | Description |
|
| 277 |
| ------ | ---- | ----------- |
|
|
|
|
| 284 |
| Project Directory | `--project` | Run the command within the given project directory |
|
| 285 |
| Requirements File | `--with-requirements` | Requirements file to install dependencies from |
|
| 286 |
|
| 287 |
+
### Entrypoints
|
| 288 |
+
|
| 289 |
+
The `install` command supports local FastMCP server files only:
|
| 290 |
+
|
| 291 |
+
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.
|
| 292 |
+
2. **Explicit server object**: `server.py:custom_name` - imports and uses the specified server object
|
| 293 |
+
3. **Factory function**: `server.py:create_server` - calls the specified function (sync or async) to create a server instance
|
| 294 |
+
|
| 295 |
+
<Note>
|
| 296 |
+
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.
|
| 297 |
+
</Note>
|
| 298 |
+
|
| 299 |
+
<Warning>
|
| 300 |
+
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.
|
| 301 |
+
</Warning>
|
| 302 |
+
|
| 303 |
**Examples**
|
| 304 |
|
| 305 |
```bash
|
|
|
|
| 337 |
fastmcp install mcp-json server.py --copy
|
| 338 |
```
|
| 339 |
|
| 340 |
+
### MCP JSON Generation
|
| 341 |
|
| 342 |
The `mcp-json` subcommand generates standard MCP JSON configuration that can be used with any MCP-compatible client. This is useful when:
|
| 343 |
|
|
|
|
| 377 |
| ------ | ---- | ----------- |
|
| 378 |
| Copy to Clipboard | `--copy` | Copy configuration to clipboard instead of printing to stdout |
|
| 379 |
|
| 380 |
+
## `fastmcp inspect`
|
| 381 |
|
| 382 |
<VersionBadge version="2.9.0" />
|
| 383 |
|
|
|
|
| 387 |
fastmcp inspect server.py
|
| 388 |
```
|
| 389 |
|
| 390 |
+
### Options
|
| 391 |
+
|
| 392 |
+
| Option | Flag | Description |
|
| 393 |
+
| ------ | ---- | ----------- |
|
| 394 |
+
| Output File | `--output`, `-o` | Output file path for the JSON report (default: server-info.json) |
|
| 395 |
+
|
| 396 |
+
### Entrypoints
|
| 397 |
+
|
| 398 |
+
The `inspect` command supports local FastMCP server files only:
|
| 399 |
+
|
| 400 |
+
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.
|
| 401 |
+
2. **Explicit server object**: `server.py:custom_name` - imports and uses the specified server object
|
| 402 |
+
3. **Factory function**: `server.py:create_server` - calls the specified function (sync or async) to create a server instance
|
| 403 |
+
|
| 404 |
+
<Warning>
|
| 405 |
+
The `inspect` command **only supports local files** - no URLs, remote servers, or MCP configuration files.
|
| 406 |
+
</Warning>
|
| 407 |
+
|
| 408 |
+
**Examples**
|
| 409 |
|
| 410 |
```bash
|
| 411 |
# Auto-detect server object
|
|
|
|
| 418 |
fastmcp inspect server.py --output analysis.json
|
| 419 |
```
|
| 420 |
|
| 421 |
+
## `fastmcp version`
|
| 422 |
|
| 423 |
Display version information about FastMCP and related components.
|
| 424 |
|
|
|
|
| 426 |
fastmcp version
|
| 427 |
```
|
| 428 |
|
| 429 |
+
### Options
|
| 430 |
|
| 431 |
| Option | Flag | Description |
|
| 432 |
| ------ | ---- | ----------- |
|
src/fastmcp/cli/cli.py
CHANGED
|
@@ -138,7 +138,7 @@ def version(
|
|
| 138 |
|
| 139 |
|
| 140 |
@app.command
|
| 141 |
-
def dev(
|
| 142 |
server_spec: str,
|
| 143 |
*,
|
| 144 |
with_editable: Annotated[
|
|
@@ -220,7 +220,7 @@ def dev(
|
|
| 220 |
|
| 221 |
try:
|
| 222 |
# Import server to get dependencies
|
| 223 |
-
server: FastMCP = run_module.import_server(file, server_object)
|
| 224 |
if server.dependencies is not None:
|
| 225 |
with_packages = list(set(with_packages + server.dependencies))
|
| 226 |
|
|
@@ -283,7 +283,7 @@ def dev(
|
|
| 283 |
|
| 284 |
|
| 285 |
@app.command
|
| 286 |
-
def run(
|
| 287 |
server_spec: str,
|
| 288 |
*server_args: str,
|
| 289 |
transport: Annotated[
|
|
@@ -414,7 +414,7 @@ def run(
|
|
| 414 |
else:
|
| 415 |
# Use direct import for backwards compatibility
|
| 416 |
try:
|
| 417 |
-
run_module.run_command(
|
| 418 |
server_spec=server_spec,
|
| 419 |
transport=transport,
|
| 420 |
host=host,
|
|
@@ -476,7 +476,7 @@ async def inspect(
|
|
| 476 |
|
| 477 |
try:
|
| 478 |
# Import the server
|
| 479 |
-
server = run_module.import_server(file, server_object)
|
| 480 |
|
| 481 |
# Get server information - using native async support
|
| 482 |
info = await inspect_fastmcp(server)
|
|
|
|
| 138 |
|
| 139 |
|
| 140 |
@app.command
|
| 141 |
+
async def dev(
|
| 142 |
server_spec: str,
|
| 143 |
*,
|
| 144 |
with_editable: Annotated[
|
|
|
|
| 220 |
|
| 221 |
try:
|
| 222 |
# Import server to get dependencies
|
| 223 |
+
server: FastMCP = await run_module.import_server(file, server_object)
|
| 224 |
if server.dependencies is not None:
|
| 225 |
with_packages = list(set(with_packages + server.dependencies))
|
| 226 |
|
|
|
|
| 283 |
|
| 284 |
|
| 285 |
@app.command
|
| 286 |
+
async def run(
|
| 287 |
server_spec: str,
|
| 288 |
*server_args: str,
|
| 289 |
transport: Annotated[
|
|
|
|
| 414 |
else:
|
| 415 |
# Use direct import for backwards compatibility
|
| 416 |
try:
|
| 417 |
+
await run_module.run_command(
|
| 418 |
server_spec=server_spec,
|
| 419 |
transport=transport,
|
| 420 |
host=host,
|
|
|
|
| 476 |
|
| 477 |
try:
|
| 478 |
# Import the server
|
| 479 |
+
server = await run_module.import_server(file, server_object)
|
| 480 |
|
| 481 |
# Get server information - using native async support
|
| 482 |
info = await inspect_fastmcp(server)
|
src/fastmcp/cli/install/claude_code.py
CHANGED
|
@@ -167,7 +167,7 @@ def install_claude_code(
|
|
| 167 |
return False
|
| 168 |
|
| 169 |
|
| 170 |
-
def claude_code_command(
|
| 171 |
server_spec: str,
|
| 172 |
*,
|
| 173 |
server_name: Annotated[
|
|
@@ -234,7 +234,7 @@ def claude_code_command(
|
|
| 234 |
Args:
|
| 235 |
server_spec: Python file to install, optionally with :object suffix
|
| 236 |
"""
|
| 237 |
-
file, server_object, name, packages, env_dict = process_common_args(
|
| 238 |
server_spec, server_name, with_packages, env_vars, env_file
|
| 239 |
)
|
| 240 |
|
|
|
|
| 167 |
return False
|
| 168 |
|
| 169 |
|
| 170 |
+
async def claude_code_command(
|
| 171 |
server_spec: str,
|
| 172 |
*,
|
| 173 |
server_name: Annotated[
|
|
|
|
| 234 |
Args:
|
| 235 |
server_spec: Python file to install, optionally with :object suffix
|
| 236 |
"""
|
| 237 |
+
file, server_object, name, packages, env_dict = await process_common_args(
|
| 238 |
server_spec, server_name, with_packages, env_vars, env_file
|
| 239 |
)
|
| 240 |
|
src/fastmcp/cli/install/claude_desktop.py
CHANGED
|
@@ -140,7 +140,7 @@ def install_claude_desktop(
|
|
| 140 |
return False
|
| 141 |
|
| 142 |
|
| 143 |
-
def claude_desktop_command(
|
| 144 |
server_spec: str,
|
| 145 |
*,
|
| 146 |
server_name: Annotated[
|
|
@@ -207,7 +207,7 @@ def claude_desktop_command(
|
|
| 207 |
Args:
|
| 208 |
server_spec: Python file to install, optionally with :object suffix
|
| 209 |
"""
|
| 210 |
-
file, server_object, name, with_packages, env_dict = process_common_args(
|
| 211 |
server_spec, server_name, with_packages, env_vars, env_file
|
| 212 |
)
|
| 213 |
|
|
|
|
| 140 |
return False
|
| 141 |
|
| 142 |
|
| 143 |
+
async def claude_desktop_command(
|
| 144 |
server_spec: str,
|
| 145 |
*,
|
| 146 |
server_name: Annotated[
|
|
|
|
| 207 |
Args:
|
| 208 |
server_spec: Python file to install, optionally with :object suffix
|
| 209 |
"""
|
| 210 |
+
file, server_object, name, with_packages, env_dict = await process_common_args(
|
| 211 |
server_spec, server_name, with_packages, env_vars, env_file
|
| 212 |
)
|
| 213 |
|
src/fastmcp/cli/install/cursor.py
CHANGED
|
@@ -150,7 +150,7 @@ def install_cursor(
|
|
| 150 |
return False
|
| 151 |
|
| 152 |
|
| 153 |
-
def cursor_command(
|
| 154 |
server_spec: str,
|
| 155 |
*,
|
| 156 |
server_name: Annotated[
|
|
@@ -217,7 +217,7 @@ def cursor_command(
|
|
| 217 |
Args:
|
| 218 |
server_spec: Python file to install, optionally with :object suffix
|
| 219 |
"""
|
| 220 |
-
file, server_object, name, with_packages, env_dict = process_common_args(
|
| 221 |
server_spec, server_name, with_packages, env_vars, env_file
|
| 222 |
)
|
| 223 |
|
|
|
|
| 150 |
return False
|
| 151 |
|
| 152 |
|
| 153 |
+
async def cursor_command(
|
| 154 |
server_spec: str,
|
| 155 |
*,
|
| 156 |
server_name: Annotated[
|
|
|
|
| 217 |
Args:
|
| 218 |
server_spec: Python file to install, optionally with :object suffix
|
| 219 |
"""
|
| 220 |
+
file, server_object, name, with_packages, env_dict = await process_common_args(
|
| 221 |
server_spec, server_name, with_packages, env_vars, env_file
|
| 222 |
)
|
| 223 |
|
src/fastmcp/cli/install/mcp_json.py
CHANGED
|
@@ -113,7 +113,7 @@ def install_mcp_json(
|
|
| 113 |
return False
|
| 114 |
|
| 115 |
|
| 116 |
-
def mcp_json_command(
|
| 117 |
server_spec: str,
|
| 118 |
*,
|
| 119 |
server_name: Annotated[
|
|
@@ -188,7 +188,7 @@ def mcp_json_command(
|
|
| 188 |
Args:
|
| 189 |
server_spec: Python file to install, optionally with :object suffix
|
| 190 |
"""
|
| 191 |
-
file, server_object, name, packages, env_dict = process_common_args(
|
| 192 |
server_spec, server_name, with_packages, env_vars, env_file
|
| 193 |
)
|
| 194 |
|
|
|
|
| 113 |
return False
|
| 114 |
|
| 115 |
|
| 116 |
+
async def mcp_json_command(
|
| 117 |
server_spec: str,
|
| 118 |
*,
|
| 119 |
server_name: Annotated[
|
|
|
|
| 188 |
Args:
|
| 189 |
server_spec: Python file to install, optionally with :object suffix
|
| 190 |
"""
|
| 191 |
+
file, server_object, name, packages, env_dict = await process_common_args(
|
| 192 |
server_spec, server_name, with_packages, env_vars, env_file
|
| 193 |
)
|
| 194 |
|
src/fastmcp/cli/install/shared.py
CHANGED
|
@@ -23,7 +23,7 @@ def parse_env_var(env_var: str) -> tuple[str, str]:
|
|
| 23 |
return key.strip(), value.strip()
|
| 24 |
|
| 25 |
|
| 26 |
-
def process_common_args(
|
| 27 |
server_spec: str,
|
| 28 |
server_name: str | None,
|
| 29 |
with_packages: list[str],
|
|
@@ -49,7 +49,7 @@ def process_common_args(
|
|
| 49 |
server = None
|
| 50 |
if not name:
|
| 51 |
try:
|
| 52 |
-
server = import_server(file, server_object)
|
| 53 |
name = server.name
|
| 54 |
except (ImportError, ModuleNotFoundError) as e:
|
| 55 |
logger.debug(
|
|
|
|
| 23 |
return key.strip(), value.strip()
|
| 24 |
|
| 25 |
|
| 26 |
+
async def process_common_args(
|
| 27 |
server_spec: str,
|
| 28 |
server_name: str | None,
|
| 29 |
with_packages: list[str],
|
|
|
|
| 49 |
server = None
|
| 50 |
if not name:
|
| 51 |
try:
|
| 52 |
+
server = await import_server(file, server_object)
|
| 53 |
name = server.name
|
| 54 |
except (ImportError, ModuleNotFoundError) as e:
|
| 55 |
logger.debug(
|
src/fastmcp/cli/run.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
"""FastMCP run command implementation with enhanced type hints."""
|
| 2 |
|
| 3 |
import importlib.util
|
|
|
|
| 4 |
import json
|
| 5 |
import re
|
| 6 |
import subprocess
|
|
@@ -58,15 +59,15 @@ def parse_file_path(server_spec: str) -> tuple[Path, str | None]:
|
|
| 58 |
return file_path, server_object
|
| 59 |
|
| 60 |
|
| 61 |
-
def import_server(file: Path,
|
| 62 |
"""Import a MCP server from a file.
|
| 63 |
|
| 64 |
Args:
|
| 65 |
file: Path to the file
|
| 66 |
-
|
| 67 |
|
| 68 |
Returns:
|
| 69 |
-
The server object
|
| 70 |
"""
|
| 71 |
# Add parent directory to Python path so imports can be resolved
|
| 72 |
file_dir = str(file.parent)
|
|
@@ -86,11 +87,12 @@ def import_server(file: Path, server_object: str | None = None) -> Any:
|
|
| 86 |
spec.loader.exec_module(module)
|
| 87 |
|
| 88 |
# If no object specified, try common server names
|
| 89 |
-
if not
|
| 90 |
-
# Look for
|
| 91 |
for name in ["mcp", "server", "app"]:
|
| 92 |
if hasattr(module, name):
|
| 93 |
-
|
|
|
|
| 94 |
|
| 95 |
logger.error(
|
| 96 |
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:
|
|
| 100 |
)
|
| 101 |
sys.exit(1)
|
| 102 |
|
| 103 |
-
assert
|
| 104 |
|
| 105 |
# Handle module:object syntax
|
| 106 |
-
if ":" in
|
| 107 |
-
module_name, object_name =
|
| 108 |
try:
|
| 109 |
server_module = importlib.import_module(module_name)
|
| 110 |
-
|
| 111 |
except ImportError:
|
| 112 |
logger.error(
|
| 113 |
f"Could not import module '{module_name}'",
|
|
@@ -116,16 +118,62 @@ def import_server(file: Path, server_object: str | None = None) -> Any:
|
|
| 116 |
sys.exit(1)
|
| 117 |
else:
|
| 118 |
# Just object name
|
| 119 |
-
|
| 120 |
|
| 121 |
-
if
|
| 122 |
logger.error(
|
| 123 |
-
f"Server object '{
|
| 124 |
extra={"file": str(file)},
|
| 125 |
)
|
| 126 |
sys.exit(1)
|
| 127 |
|
| 128 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
|
| 131 |
def run_with_uv(
|
|
@@ -219,7 +267,7 @@ def create_client_server(url: str) -> Any:
|
|
| 219 |
import fastmcp
|
| 220 |
|
| 221 |
client = fastmcp.Client(url)
|
| 222 |
-
server = fastmcp.FastMCP.
|
| 223 |
return server
|
| 224 |
except Exception as e:
|
| 225 |
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]:
|
|
| 237 |
return server
|
| 238 |
|
| 239 |
|
| 240 |
-
def import_server_with_args(
|
| 241 |
-
file: Path,
|
|
|
|
|
|
|
| 242 |
) -> Any:
|
| 243 |
"""Import a server with optional command line arguments.
|
| 244 |
|
| 245 |
Args:
|
| 246 |
file: Path to the server file
|
| 247 |
-
|
| 248 |
server_args: Optional command line arguments to inject
|
| 249 |
|
| 250 |
Returns:
|
|
@@ -254,14 +304,14 @@ def import_server_with_args(
|
|
| 254 |
original_argv = sys.argv[:]
|
| 255 |
try:
|
| 256 |
sys.argv = [str(file)] + server_args
|
| 257 |
-
return import_server(file,
|
| 258 |
finally:
|
| 259 |
sys.argv = original_argv
|
| 260 |
else:
|
| 261 |
-
return import_server(file,
|
| 262 |
|
| 263 |
|
| 264 |
-
def run_command(
|
| 265 |
server_spec: str,
|
| 266 |
transport: TransportType | None = None,
|
| 267 |
host: str | None = None,
|
|
@@ -293,8 +343,8 @@ def run_command(
|
|
| 293 |
server = create_mcp_config_server(Path(server_spec))
|
| 294 |
else:
|
| 295 |
# Handle file case
|
| 296 |
-
file,
|
| 297 |
-
server = import_server_with_args(file,
|
| 298 |
logger.debug(f'Found server "{server.name}" in {file}')
|
| 299 |
|
| 300 |
# Run the server
|
|
@@ -320,7 +370,7 @@ def run_command(
|
|
| 320 |
kwargs["show_banner"] = False
|
| 321 |
|
| 322 |
try:
|
| 323 |
-
server.
|
| 324 |
except Exception as e:
|
| 325 |
logger.error(f"Failed to run server: {e}")
|
| 326 |
sys.exit(1)
|
|
|
|
| 1 |
"""FastMCP run command implementation with enhanced type hints."""
|
| 2 |
|
| 3 |
import importlib.util
|
| 4 |
+
import inspect
|
| 5 |
import json
|
| 6 |
import re
|
| 7 |
import subprocess
|
|
|
|
| 59 |
return file_path, server_object
|
| 60 |
|
| 61 |
|
| 62 |
+
async def import_server(file: Path, server_or_factory: str | None = None) -> Any:
|
| 63 |
"""Import a MCP server from a file.
|
| 64 |
|
| 65 |
Args:
|
| 66 |
file: Path to the file
|
| 67 |
+
server_or_factory: Optional object name in format "module:object" or just "object"
|
| 68 |
|
| 69 |
Returns:
|
| 70 |
+
The server object (or result of calling a factory function)
|
| 71 |
"""
|
| 72 |
# Add parent directory to Python path so imports can be resolved
|
| 73 |
file_dir = str(file.parent)
|
|
|
|
| 87 |
spec.loader.exec_module(module)
|
| 88 |
|
| 89 |
# If no object specified, try common server names
|
| 90 |
+
if not server_or_factory:
|
| 91 |
+
# Look for common server instance names
|
| 92 |
for name in ["mcp", "server", "app"]:
|
| 93 |
if hasattr(module, name):
|
| 94 |
+
obj = getattr(module, name)
|
| 95 |
+
return await _resolve_server_or_factory(obj, file, name)
|
| 96 |
|
| 97 |
logger.error(
|
| 98 |
f"No server object found in {file}. Please either:\n"
|
|
|
|
| 102 |
)
|
| 103 |
sys.exit(1)
|
| 104 |
|
| 105 |
+
assert server_or_factory is not None
|
| 106 |
|
| 107 |
# Handle module:object syntax
|
| 108 |
+
if ":" in server_or_factory:
|
| 109 |
+
module_name, object_name = server_or_factory.split(":", 1)
|
| 110 |
try:
|
| 111 |
server_module = importlib.import_module(module_name)
|
| 112 |
+
obj = getattr(server_module, object_name, None)
|
| 113 |
except ImportError:
|
| 114 |
logger.error(
|
| 115 |
f"Could not import module '{module_name}'",
|
|
|
|
| 118 |
sys.exit(1)
|
| 119 |
else:
|
| 120 |
# Just object name
|
| 121 |
+
obj = getattr(module, server_or_factory, None)
|
| 122 |
|
| 123 |
+
if obj is None:
|
| 124 |
logger.error(
|
| 125 |
+
f"Server object '{server_or_factory}' not found",
|
| 126 |
extra={"file": str(file)},
|
| 127 |
)
|
| 128 |
sys.exit(1)
|
| 129 |
|
| 130 |
+
return await _resolve_server_or_factory(obj, file, server_or_factory)
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
async def _resolve_server_or_factory(obj: Any, file: Path, name: str) -> Any:
|
| 134 |
+
"""Resolve a server object or factory function to a server instance.
|
| 135 |
+
|
| 136 |
+
Args:
|
| 137 |
+
obj: The object that might be a server or factory function
|
| 138 |
+
file: Path to the file for error messages
|
| 139 |
+
name: Name of the object for error messages
|
| 140 |
+
|
| 141 |
+
Returns:
|
| 142 |
+
A server instance
|
| 143 |
+
"""
|
| 144 |
+
# Check if it's a function or coroutine function
|
| 145 |
+
if inspect.isfunction(obj) or inspect.iscoroutinefunction(obj):
|
| 146 |
+
logger.debug(f"Found factory function '{name}' in {file}")
|
| 147 |
+
|
| 148 |
+
try:
|
| 149 |
+
if inspect.iscoroutinefunction(obj):
|
| 150 |
+
# Async factory function
|
| 151 |
+
server = await obj()
|
| 152 |
+
else:
|
| 153 |
+
# Sync factory function
|
| 154 |
+
server = obj()
|
| 155 |
+
|
| 156 |
+
# Validate the result is a FastMCP server
|
| 157 |
+
if not isinstance(server, FastMCP | FastMCP1x):
|
| 158 |
+
logger.error(
|
| 159 |
+
f"Factory function '{name}' must return a FastMCP server instance, "
|
| 160 |
+
f"got {type(server).__name__}",
|
| 161 |
+
extra={"file": str(file)},
|
| 162 |
+
)
|
| 163 |
+
sys.exit(1)
|
| 164 |
+
|
| 165 |
+
logger.debug(f"Factory function '{name}' created server: {server.name}")
|
| 166 |
+
return server
|
| 167 |
+
|
| 168 |
+
except Exception as e:
|
| 169 |
+
logger.error(
|
| 170 |
+
f"Failed to call factory function '{name}': {e}",
|
| 171 |
+
extra={"file": str(file)},
|
| 172 |
+
)
|
| 173 |
+
sys.exit(1)
|
| 174 |
+
|
| 175 |
+
# Not a function, return as-is (should be a server instance)
|
| 176 |
+
return obj
|
| 177 |
|
| 178 |
|
| 179 |
def run_with_uv(
|
|
|
|
| 267 |
import fastmcp
|
| 268 |
|
| 269 |
client = fastmcp.Client(url)
|
| 270 |
+
server = fastmcp.FastMCP.as_proxy(client)
|
| 271 |
return server
|
| 272 |
except Exception as e:
|
| 273 |
logger.error(f"Failed to create client for URL {url}: {e}")
|
|
|
|
| 285 |
return server
|
| 286 |
|
| 287 |
|
| 288 |
+
async def import_server_with_args(
|
| 289 |
+
file: Path,
|
| 290 |
+
server_or_factory: str | None = None,
|
| 291 |
+
server_args: list[str] | None = None,
|
| 292 |
) -> Any:
|
| 293 |
"""Import a server with optional command line arguments.
|
| 294 |
|
| 295 |
Args:
|
| 296 |
file: Path to the server file
|
| 297 |
+
server_or_factory: Optional server object or factory function name
|
| 298 |
server_args: Optional command line arguments to inject
|
| 299 |
|
| 300 |
Returns:
|
|
|
|
| 304 |
original_argv = sys.argv[:]
|
| 305 |
try:
|
| 306 |
sys.argv = [str(file)] + server_args
|
| 307 |
+
return await import_server(file, server_or_factory)
|
| 308 |
finally:
|
| 309 |
sys.argv = original_argv
|
| 310 |
else:
|
| 311 |
+
return await import_server(file, server_or_factory)
|
| 312 |
|
| 313 |
|
| 314 |
+
async def run_command(
|
| 315 |
server_spec: str,
|
| 316 |
transport: TransportType | None = None,
|
| 317 |
host: str | None = None,
|
|
|
|
| 343 |
server = create_mcp_config_server(Path(server_spec))
|
| 344 |
else:
|
| 345 |
# Handle file case
|
| 346 |
+
file, server_or_factory = parse_file_path(server_spec)
|
| 347 |
+
server = await import_server_with_args(file, server_or_factory, server_args)
|
| 348 |
logger.debug(f'Found server "{server.name}" in {file}')
|
| 349 |
|
| 350 |
# Run the server
|
|
|
|
| 370 |
kwargs["show_banner"] = False
|
| 371 |
|
| 372 |
try:
|
| 373 |
+
await server.run_async(**kwargs)
|
| 374 |
except Exception as e:
|
| 375 |
logger.error(f"Failed to run server: {e}")
|
| 376 |
sys.exit(1)
|
tests/cli/test_cursor.py
CHANGED
|
@@ -306,7 +306,7 @@ class TestCursorCommand:
|
|
| 306 |
|
| 307 |
@patch("fastmcp.cli.install.cursor.install_cursor")
|
| 308 |
@patch("fastmcp.cli.install.cursor.process_common_args")
|
| 309 |
-
def test_cursor_command_basic(self, mock_process_args, mock_install):
|
| 310 |
"""Test basic cursor command execution."""
|
| 311 |
mock_process_args.return_value = (
|
| 312 |
Path("server.py"),
|
|
@@ -318,7 +318,7 @@ class TestCursorCommand:
|
|
| 318 |
mock_install.return_value = True
|
| 319 |
|
| 320 |
with patch("sys.exit") as mock_exit:
|
| 321 |
-
cursor_command("server.py")
|
| 322 |
|
| 323 |
mock_install.assert_called_once_with(
|
| 324 |
file=Path("server.py"),
|
|
@@ -335,7 +335,7 @@ class TestCursorCommand:
|
|
| 335 |
|
| 336 |
@patch("fastmcp.cli.install.cursor.install_cursor")
|
| 337 |
@patch("fastmcp.cli.install.cursor.process_common_args")
|
| 338 |
-
def test_cursor_command_failure(self, mock_process_args, mock_install):
|
| 339 |
"""Test cursor command when installation fails."""
|
| 340 |
mock_process_args.return_value = (
|
| 341 |
Path("server.py"),
|
|
@@ -347,6 +347,6 @@ class TestCursorCommand:
|
|
| 347 |
mock_install.return_value = False
|
| 348 |
|
| 349 |
with pytest.raises(SystemExit) as exc_info:
|
| 350 |
-
cursor_command("server.py")
|
| 351 |
|
| 352 |
assert exc_info.value.code == 1
|
|
|
|
| 306 |
|
| 307 |
@patch("fastmcp.cli.install.cursor.install_cursor")
|
| 308 |
@patch("fastmcp.cli.install.cursor.process_common_args")
|
| 309 |
+
async def test_cursor_command_basic(self, mock_process_args, mock_install):
|
| 310 |
"""Test basic cursor command execution."""
|
| 311 |
mock_process_args.return_value = (
|
| 312 |
Path("server.py"),
|
|
|
|
| 318 |
mock_install.return_value = True
|
| 319 |
|
| 320 |
with patch("sys.exit") as mock_exit:
|
| 321 |
+
await cursor_command("server.py")
|
| 322 |
|
| 323 |
mock_install.assert_called_once_with(
|
| 324 |
file=Path("server.py"),
|
|
|
|
| 335 |
|
| 336 |
@patch("fastmcp.cli.install.cursor.install_cursor")
|
| 337 |
@patch("fastmcp.cli.install.cursor.process_common_args")
|
| 338 |
+
async def test_cursor_command_failure(self, mock_process_args, mock_install):
|
| 339 |
"""Test cursor command when installation fails."""
|
| 340 |
mock_process_args.return_value = (
|
| 341 |
Path("server.py"),
|
|
|
|
| 347 |
mock_install.return_value = False
|
| 348 |
|
| 349 |
with pytest.raises(SystemExit) as exc_info:
|
| 350 |
+
await cursor_command("server.py")
|
| 351 |
|
| 352 |
assert exc_info.value.code == 1
|
tests/cli/test_run.py
CHANGED
|
@@ -157,7 +157,7 @@ def greet(name: str) -> str:
|
|
| 157 |
return f"Hello, {name}!"
|
| 158 |
""")
|
| 159 |
|
| 160 |
-
server = import_server(test_file)
|
| 161 |
assert server.name == "TestServer"
|
| 162 |
tools = await server.get_tools()
|
| 163 |
assert "greet" in tools
|
|
@@ -178,12 +178,12 @@ if __name__ == "__main__":
|
|
| 178 |
app.run()
|
| 179 |
""")
|
| 180 |
|
| 181 |
-
server = import_server(test_file)
|
| 182 |
assert server.name == "MainServer"
|
| 183 |
tools = await server.get_tools()
|
| 184 |
assert "calculate" in tools
|
| 185 |
|
| 186 |
-
def test_import_server_standard_names(self, tmp_path):
|
| 187 |
"""Test automatic detection of standard names (mcp, server, app)."""
|
| 188 |
# Test with 'mcp' name
|
| 189 |
mcp_file = tmp_path / "mcp_server.py"
|
|
@@ -192,7 +192,7 @@ import fastmcp
|
|
| 192 |
mcp = fastmcp.FastMCP("MCPServer")
|
| 193 |
""")
|
| 194 |
|
| 195 |
-
server = import_server(mcp_file)
|
| 196 |
assert server.name == "MCPServer"
|
| 197 |
|
| 198 |
# Test with 'server' name
|
|
@@ -202,7 +202,7 @@ import fastmcp
|
|
| 202 |
server = fastmcp.FastMCP("ServerServer")
|
| 203 |
""")
|
| 204 |
|
| 205 |
-
server = import_server(server_file)
|
| 206 |
assert server.name == "ServerServer"
|
| 207 |
|
| 208 |
# Test with 'app' name
|
|
@@ -212,7 +212,7 @@ import fastmcp
|
|
| 212 |
app = fastmcp.FastMCP("AppServer")
|
| 213 |
""")
|
| 214 |
|
| 215 |
-
server = import_server(app_file)
|
| 216 |
assert server.name == "AppServer"
|
| 217 |
|
| 218 |
async def test_import_server_nonstandard_name(self, tmp_path):
|
|
@@ -228,12 +228,12 @@ def custom_tool() -> str:
|
|
| 228 |
return "custom"
|
| 229 |
""")
|
| 230 |
|
| 231 |
-
server = import_server(test_file, "my_custom_server")
|
| 232 |
assert server.name == "CustomServer"
|
| 233 |
tools = await server.get_tools()
|
| 234 |
assert "custom_tool" in tools
|
| 235 |
|
| 236 |
-
def test_import_server_no_standard_names_fails(self, tmp_path):
|
| 237 |
"""Test importing server when no standard names exist fails."""
|
| 238 |
test_file = tmp_path / "server.py"
|
| 239 |
test_file.write_text("""
|
|
@@ -243,10 +243,10 @@ other_name = fastmcp.FastMCP("OtherServer")
|
|
| 243 |
""")
|
| 244 |
|
| 245 |
with pytest.raises(SystemExit) as exc_info:
|
| 246 |
-
import_server(test_file)
|
| 247 |
assert exc_info.value.code == 1
|
| 248 |
|
| 249 |
-
def test_import_server_nonexistent_object_fails(self, tmp_path):
|
| 250 |
"""Test importing nonexistent server object fails."""
|
| 251 |
test_file = tmp_path / "server.py"
|
| 252 |
test_file.write_text("""
|
|
@@ -256,5 +256,5 @@ mcp = fastmcp.FastMCP("TestServer")
|
|
| 256 |
""")
|
| 257 |
|
| 258 |
with pytest.raises(SystemExit) as exc_info:
|
| 259 |
-
import_server(test_file, "nonexistent")
|
| 260 |
assert exc_info.value.code == 1
|
|
|
|
| 157 |
return f"Hello, {name}!"
|
| 158 |
""")
|
| 159 |
|
| 160 |
+
server = await import_server(test_file)
|
| 161 |
assert server.name == "TestServer"
|
| 162 |
tools = await server.get_tools()
|
| 163 |
assert "greet" in tools
|
|
|
|
| 178 |
app.run()
|
| 179 |
""")
|
| 180 |
|
| 181 |
+
server = await import_server(test_file)
|
| 182 |
assert server.name == "MainServer"
|
| 183 |
tools = await server.get_tools()
|
| 184 |
assert "calculate" in tools
|
| 185 |
|
| 186 |
+
async def test_import_server_standard_names(self, tmp_path):
|
| 187 |
"""Test automatic detection of standard names (mcp, server, app)."""
|
| 188 |
# Test with 'mcp' name
|
| 189 |
mcp_file = tmp_path / "mcp_server.py"
|
|
|
|
| 192 |
mcp = fastmcp.FastMCP("MCPServer")
|
| 193 |
""")
|
| 194 |
|
| 195 |
+
server = await import_server(mcp_file)
|
| 196 |
assert server.name == "MCPServer"
|
| 197 |
|
| 198 |
# Test with 'server' name
|
|
|
|
| 202 |
server = fastmcp.FastMCP("ServerServer")
|
| 203 |
""")
|
| 204 |
|
| 205 |
+
server = await import_server(server_file)
|
| 206 |
assert server.name == "ServerServer"
|
| 207 |
|
| 208 |
# Test with 'app' name
|
|
|
|
| 212 |
app = fastmcp.FastMCP("AppServer")
|
| 213 |
""")
|
| 214 |
|
| 215 |
+
server = await import_server(app_file)
|
| 216 |
assert server.name == "AppServer"
|
| 217 |
|
| 218 |
async def test_import_server_nonstandard_name(self, tmp_path):
|
|
|
|
| 228 |
return "custom"
|
| 229 |
""")
|
| 230 |
|
| 231 |
+
server = await import_server(test_file, "my_custom_server")
|
| 232 |
assert server.name == "CustomServer"
|
| 233 |
tools = await server.get_tools()
|
| 234 |
assert "custom_tool" in tools
|
| 235 |
|
| 236 |
+
async def test_import_server_no_standard_names_fails(self, tmp_path):
|
| 237 |
"""Test importing server when no standard names exist fails."""
|
| 238 |
test_file = tmp_path / "server.py"
|
| 239 |
test_file.write_text("""
|
|
|
|
| 243 |
""")
|
| 244 |
|
| 245 |
with pytest.raises(SystemExit) as exc_info:
|
| 246 |
+
await import_server(test_file)
|
| 247 |
assert exc_info.value.code == 1
|
| 248 |
|
| 249 |
+
async def test_import_server_nonexistent_object_fails(self, tmp_path):
|
| 250 |
"""Test importing nonexistent server object fails."""
|
| 251 |
test_file = tmp_path / "server.py"
|
| 252 |
test_file.write_text("""
|
|
|
|
| 256 |
""")
|
| 257 |
|
| 258 |
with pytest.raises(SystemExit) as exc_info:
|
| 259 |
+
await import_server(test_file, "nonexistent")
|
| 260 |
assert exc_info.value.code == 1
|