Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions src/anaconda_mcp/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,16 @@ def cli(ctx, verbose: bool):
help="Path to mcp_compose.toml file (default: src/anaconda_mcp/mcp_compose.toml)",
)
@click.option("--host", default="0.0.0.0", show_default=True, help="Host to bind to.")
@click.option("--port", default=8000, show_default=True, type=int, help="Port to bind to.")
@click.option("--port", default=None, type=int, help="Port to bind to (default: read from config file).")
@click.option("--delay", default=0, show_default=True, type=int, help="Delay in seconds added before serving")
@click.option(
"--transport",
type=click.Choice(["stdio", "sse", "streamable-http"]),
default=None,
help="Transport override (default: read from config file).",
)
@click.pass_context
def serve(ctx, config, host, port, delay):
def serve(ctx, config, host, port, delay, transport):
def _handle_sigterm(signum, frame):
logger.info("Received SIGTERM, shutting down...")
sys.exit(0)
Expand Down Expand Up @@ -125,7 +131,10 @@ def _handle_sigterm(signum, frame):
)
patch_tool_call_tracking(bearer_token_fn=get_auth_token)
try:
ns = _ns(verbose=ctx.obj["verbose"], config=rendered_config, host=host, port=port)
ns_kwargs = {"verbose": ctx.obj["verbose"], "config": rendered_config, "host": host, "transport": transport}
if port is not None:
ns_kwargs["port"] = port
ns = _ns(**ns_kwargs)
sys.exit(_serve(ns))
except Exception:
logger.exception("MCP Composer returned an error. Exiting", exc_info=True)
Expand Down
48 changes: 48 additions & 0 deletions tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,54 @@ async def test_serve_should_start_auth_flow(mock_start_login, mock_serve_command
assert mock_serve_command.call_count == 1


async def test_serve_transport_flag_is_passed_to_serve_command(mock_start_login, mock_serve_command):
# Given
runner = CliRunner()
with mock.patch("anaconda_mcp.cli.Path.exists", return_value=True):
result = runner.invoke(cli, ["serve", "--transport", "streamable-http"])

# Then
assert result.exit_code == 0
ns = mock_serve_command.call_args[0][0]
assert ns.transport == "streamable-http"


async def test_serve_transport_flag_defaults_to_none(mock_start_login, mock_serve_command):
# Given
runner = CliRunner()
with mock.patch("anaconda_mcp.cli.Path.exists", return_value=True):
result = runner.invoke(cli, ["serve"])

# Then
assert result.exit_code == 0
ns = mock_serve_command.call_args[0][0]
assert ns.transport is None


async def test_serve_port_flag_is_passed_to_serve_command(mock_start_login, mock_serve_command):
# Given
runner = CliRunner()
with mock.patch("anaconda_mcp.cli.Path.exists", return_value=True):
result = runner.invoke(cli, ["serve", "--port", "9000"])

# Then
assert result.exit_code == 0
ns = mock_serve_command.call_args[0][0]
assert ns.port == 9000


async def test_serve_port_omitted_leaves_config_in_control(mock_start_login, mock_serve_command):
# Given
runner = CliRunner()
with mock.patch("anaconda_mcp.cli.Path.exists", return_value=True):
result = runner.invoke(cli, ["serve"])

# Then — port not in ns, so mcp-compose falls back to toml
assert result.exit_code == 0
ns = mock_serve_command.call_args[0][0]
assert not hasattr(ns, "port")


async def test_start_login_times_out_without_token(mocked_init_telemetry, mock_get_auth_token, mock_anaconda_login):
# Given - no token available
auth._initialized = False
Expand Down
Loading