Jeremiah Lowin commited on
Commit
a4ec518
·
unverified ·
1 Parent(s): 0d61263

Support factory functions in fastmcp run (#1384)

Browse files
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 | Default: 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 | 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
- ## Command Details
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
- #### Options
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
- #### Server Specification
58
  <VersionBadge version="2.3.5" />
59
 
60
- The server can be specified in four ways:
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
- <Tip>
67
- 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.
68
- </Tip>
 
 
69
 
70
- For example, if your code contains:
 
 
 
 
71
 
72
- ```python
73
- # server.py
 
 
 
 
 
 
 
 
74
  from fastmcp import FastMCP
75
 
76
  mcp = FastMCP("MyServer")
 
77
 
78
- @mcp.tool
79
- def hello(name: str) -> str:
80
- return f"Hello, {name}!"
81
 
82
- if __name__ == "__main__":
83
- # This is ignored when using `fastmcp run`!
84
- mcp.run(transport="stdio")
85
  ```
86
 
87
- You can run it with Streamable HTTP transport regardless of what's in the `__main__` block:
 
 
88
 
89
  ```bash
90
- fastmcp run server.py --transport http --port 8000
91
  ```
92
 
93
- **Examples**
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
  ```bash
96
- # Run a local server with Streamable HTTP transport on a custom port
97
- fastmcp run server.py --transport http --port 8000
98
 
99
- # Connect to a remote server and proxy as a stdio server
100
- fastmcp run https://example.com/mcp-server
101
 
102
- # Connect to a remote server with specified log level
103
- fastmcp run https://example.com/mcp-server --log-level DEBUG
104
 
105
- # Run with a specific Python version
106
- fastmcp run server.py --python 3.11
107
 
108
- # Run with additional packages
109
- fastmcp run server.py --with pandas --with numpy
110
 
111
- # Run within a specific project directory
112
- fastmcp run server.py --project /path/to/project
113
 
114
- # Run with dependencies from a requirements file
115
- fastmcp run server.py --with-requirements requirements.txt
 
 
 
 
 
 
 
 
 
 
116
  ```
117
 
118
- #### Running MCP Configuration Files
119
 
120
- 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.
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
- **Run the configuration:**
 
 
 
 
 
145
  ```bash
146
- # Run with default stdio transport
147
- fastmcp run mcp.json
 
 
 
 
148
 
149
- # Run with HTTP transport on custom port
150
- fastmcp run mcp.json --transport http --port 8080
151
 
152
- # Run with SSE transport
153
- fastmcp run mcp.json --transport sse
154
  ```
155
 
156
- ### `dev`
 
 
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
- #### Options
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
- ### `install`
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
- #### Server Specification
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
- #### MCP JSON Generation
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
- ### `inspect`
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
- The command supports the same server specification format as `run` and `install`:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- ### `version`
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
- #### Options
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, server_object: str | None = None) -> Any:
62
  """Import a MCP server from a file.
63
 
64
  Args:
65
  file: Path to the file
66
- server_object: Optional object name in format "module:object" or just "object"
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 server_object:
90
- # Look for the most common server object names
91
  for name in ["mcp", "server", "app"]:
92
  if hasattr(module, name):
93
- return getattr(module, name)
 
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 server_object is not None
104
 
105
  # Handle module:object syntax
106
- if ":" in server_object:
107
- module_name, object_name = server_object.split(":", 1)
108
  try:
109
  server_module = importlib.import_module(module_name)
110
- server = getattr(server_module, object_name, None)
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
- server = getattr(module, server_object, None)
120
 
121
- if server is None:
122
  logger.error(
123
- f"Server object '{server_object}' not found",
124
  extra={"file": str(file)},
125
  )
126
  sys.exit(1)
127
 
128
- return server
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.from_client(client)
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, server_object: str | None = None, server_args: list[str] | None = None
 
 
242
  ) -> Any:
243
  """Import a server with optional command line arguments.
244
 
245
  Args:
246
  file: Path to the server file
247
- server_object: Optional server object name
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, server_object)
258
  finally:
259
  sys.argv = original_argv
260
  else:
261
- return import_server(file, server_object)
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, server_object = parse_file_path(server_spec)
297
- server = import_server_with_args(file, server_object, server_args)
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.run(**kwargs)
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