Spaces:
Running
Running
Fix server argument passing in CLI run command (#1293)
Browse filesCo-authored-by: Tapan Chugh <tapanc@cs.washington.edu>
- src/fastmcp/cli/cli.py +3 -6
- tests/cli/test_cli.py +73 -0
src/fastmcp/cli/cli.py
CHANGED
|
@@ -285,7 +285,7 @@ def dev(
|
|
| 285 |
@app.command
|
| 286 |
def run(
|
| 287 |
server_spec: str,
|
| 288 |
-
*,
|
| 289 |
transport: Annotated[
|
| 290 |
run_module.TransportType | None,
|
| 291 |
cyclopts.Parameter(
|
|
@@ -373,9 +373,6 @@ def run(
|
|
| 373 |
Args:
|
| 374 |
server_spec: Python file, object specification (file:obj), MCPConfig file, or URL
|
| 375 |
"""
|
| 376 |
-
# TODO: Handle server_args from extra context
|
| 377 |
-
server_args = [] # Will need to handle this with Cyclopts context
|
| 378 |
-
|
| 379 |
logger.debug(
|
| 380 |
"Running server or client",
|
| 381 |
extra={
|
|
@@ -385,7 +382,7 @@ def run(
|
|
| 385 |
"port": port,
|
| 386 |
"path": path,
|
| 387 |
"log_level": log_level,
|
| 388 |
-
"server_args": server_args,
|
| 389 |
},
|
| 390 |
)
|
| 391 |
|
|
@@ -424,7 +421,7 @@ def run(
|
|
| 424 |
port=port,
|
| 425 |
path=path,
|
| 426 |
log_level=log_level,
|
| 427 |
-
server_args=server_args,
|
| 428 |
show_banner=not no_banner,
|
| 429 |
)
|
| 430 |
except Exception as e:
|
|
|
|
| 285 |
@app.command
|
| 286 |
def run(
|
| 287 |
server_spec: str,
|
| 288 |
+
*server_args: str,
|
| 289 |
transport: Annotated[
|
| 290 |
run_module.TransportType | None,
|
| 291 |
cyclopts.Parameter(
|
|
|
|
| 373 |
Args:
|
| 374 |
server_spec: Python file, object specification (file:obj), MCPConfig file, or URL
|
| 375 |
"""
|
|
|
|
|
|
|
|
|
|
| 376 |
logger.debug(
|
| 377 |
"Running server or client",
|
| 378 |
extra={
|
|
|
|
| 382 |
"port": port,
|
| 383 |
"path": path,
|
| 384 |
"log_level": log_level,
|
| 385 |
+
"server_args": list(server_args),
|
| 386 |
},
|
| 387 |
)
|
| 388 |
|
|
|
|
| 421 |
port=port,
|
| 422 |
path=path,
|
| 423 |
log_level=log_level,
|
| 424 |
+
server_args=list(server_args),
|
| 425 |
show_banner=not no_banner,
|
| 426 |
)
|
| 427 |
except Exception as e:
|
tests/cli/test_cli.py
CHANGED
|
@@ -399,6 +399,79 @@ class TestRunCommand:
|
|
| 399 |
assert command is not None
|
| 400 |
assert bound.arguments["transport"] == "streamable-http"
|
| 401 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 402 |
|
| 403 |
class TestWindowsSpecific:
|
| 404 |
"""Test Windows-specific functionality."""
|
|
|
|
| 399 |
assert command is not None
|
| 400 |
assert bound.arguments["transport"] == "streamable-http"
|
| 401 |
|
| 402 |
+
def test_run_command_parsing_with_server_args(self):
|
| 403 |
+
"""Test run command parsing with server arguments after --."""
|
| 404 |
+
command, bound, _ = app.parse_args(
|
| 405 |
+
[
|
| 406 |
+
"run",
|
| 407 |
+
"server.py",
|
| 408 |
+
"--",
|
| 409 |
+
"--config",
|
| 410 |
+
"test.json",
|
| 411 |
+
"--debug",
|
| 412 |
+
]
|
| 413 |
+
)
|
| 414 |
+
|
| 415 |
+
assert command is not None
|
| 416 |
+
assert bound.arguments["server_spec"] == "server.py"
|
| 417 |
+
# Server args after -- are captured as positional arguments in bound.args
|
| 418 |
+
assert bound.args == ("server.py", "--config", "test.json", "--debug")
|
| 419 |
+
|
| 420 |
+
def test_run_command_parsing_with_mixed_args(self):
|
| 421 |
+
"""Test run command parsing with both FastMCP options and server args."""
|
| 422 |
+
command, bound, _ = app.parse_args(
|
| 423 |
+
[
|
| 424 |
+
"run",
|
| 425 |
+
"server.py",
|
| 426 |
+
"--transport",
|
| 427 |
+
"http",
|
| 428 |
+
"--port",
|
| 429 |
+
"8080",
|
| 430 |
+
"--",
|
| 431 |
+
"--server-port",
|
| 432 |
+
"9090",
|
| 433 |
+
"--debug",
|
| 434 |
+
]
|
| 435 |
+
)
|
| 436 |
+
|
| 437 |
+
assert command is not None
|
| 438 |
+
assert bound.arguments["server_spec"] == "server.py"
|
| 439 |
+
assert bound.arguments["transport"] == "http"
|
| 440 |
+
assert bound.arguments["port"] == 8080
|
| 441 |
+
# Server args after -- are captured separately from FastMCP options
|
| 442 |
+
assert bound.args == ("server.py", "--server-port", "9090", "--debug")
|
| 443 |
+
|
| 444 |
+
def test_run_command_parsing_with_positional_server_args(self):
|
| 445 |
+
"""Test run command parsing with positional server arguments."""
|
| 446 |
+
command, bound, _ = app.parse_args(
|
| 447 |
+
[
|
| 448 |
+
"run",
|
| 449 |
+
"server.py",
|
| 450 |
+
"--",
|
| 451 |
+
"arg1",
|
| 452 |
+
"arg2",
|
| 453 |
+
"--flag",
|
| 454 |
+
]
|
| 455 |
+
)
|
| 456 |
+
|
| 457 |
+
assert command is not None
|
| 458 |
+
assert bound.arguments["server_spec"] == "server.py"
|
| 459 |
+
# Positional args and flags after -- are all captured
|
| 460 |
+
assert bound.args == ("server.py", "arg1", "arg2", "--flag")
|
| 461 |
+
|
| 462 |
+
def test_run_command_parsing_server_args_require_delimiter(self):
|
| 463 |
+
"""Test that server args without -- delimiter are rejected."""
|
| 464 |
+
# Should fail because --config is not a recognized FastMCP option
|
| 465 |
+
with pytest.raises(SystemExit):
|
| 466 |
+
app.parse_args(
|
| 467 |
+
[
|
| 468 |
+
"run",
|
| 469 |
+
"server.py",
|
| 470 |
+
"--config",
|
| 471 |
+
"test.json",
|
| 472 |
+
]
|
| 473 |
+
)
|
| 474 |
+
|
| 475 |
|
| 476 |
class TestWindowsSpecific:
|
| 477 |
"""Test Windows-specific functionality."""
|