From e05ba2965db5373c14ada7ddb3740d9b59a28206 Mon Sep 17 00:00:00 2001 From: David Duong Date: Thu, 11 Jun 2026 02:10:12 +0200 Subject: [PATCH] feat(cli): add support for passing certfile and cert key to run dev server under HTTPS (#8031) This allows running the dev server under HTTPS, which is a requirement for running local server under Safari. --- libs/cli/langgraph_cli/cli.py | 38 +++++++++++++++++++++++ libs/cli/tests/unit_tests/cli/test_cli.py | 25 +++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/libs/cli/langgraph_cli/cli.py b/libs/cli/langgraph_cli/cli.py index 6e6950bd4..d40ee5bcd 100644 --- a/libs/cli/langgraph_cli/cli.py +++ b/libs/cli/langgraph_cli/cli.py @@ -724,6 +724,30 @@ def dockerfile( default="WARNING", help="Set the log level for the API server.", ) +@click.option( + "--ssl-certfile", + type=click.Path( + exists=True, + file_okay=True, + dir_okay=False, + resolve_path=True, + path_type=pathlib.Path, + ), + default=None, + help="Path to an SSL certificate file for serving the development server over HTTPS.", +) +@click.option( + "--ssl-keyfile", + type=click.Path( + exists=True, + file_okay=True, + dir_okay=False, + resolve_path=True, + path_type=pathlib.Path, + ), + default=None, + help="Path to an SSL key file for serving the development server over HTTPS.", +) @cli.command( "dev", help="🏃‍♀️‍➡️ Run LangGraph API server in development mode with hot reloading and debugging support", @@ -742,8 +766,20 @@ def dev( allow_blocking: bool, tunnel: bool, server_log_level: str, + ssl_certfile: pathlib.Path | None, + ssl_keyfile: pathlib.Path | None, ): """CLI entrypoint for running the LangGraph API server.""" + if (ssl_certfile is None) != (ssl_keyfile is None): + raise click.UsageError( + "Both --ssl-certfile and --ssl-keyfile must be provided to enable HTTPS." + ) + + if ssl_certfile and ssl_keyfile and tunnel: + raise click.UsageError( + "Cannot use --tunnel with SSL options. Please choose either to serve over HTTPS or to expose via a tunnel, but not both." + ) + try: from langgraph_api.cli import run_server # type: ignore except ImportError: @@ -814,6 +850,8 @@ def dev( server_level=server_log_level, checkpointer=config_json.get("checkpointer"), disable_persistence=config_json.get("disable_persistence", False), + ssl_certfile=ssl_certfile, + ssl_keyfile=ssl_keyfile, ) diff --git a/libs/cli/tests/unit_tests/cli/test_cli.py b/libs/cli/tests/unit_tests/cli/test_cli.py index c30a74293..8b3ae47c6 100644 --- a/libs/cli/tests/unit_tests/cli/test_cli.py +++ b/libs/cli/tests/unit_tests/cli/test_cli.py @@ -320,6 +320,31 @@ def test_top_level_help_truncates_command_descriptions_to_single_line() -> None: assert "[Beta] List LangSmith Deployments." in deploy_list_line +def test_dev_command_requires_ssl_certfile_and_keyfile_together(tmp_path) -> None: + config_path = tmp_path / "langgraph.json" + config_path.write_text( + json.dumps({"dependencies": [], "graphs": {"agent": "./agent.py:graph"}}), + encoding="utf-8", + ) + certfile = tmp_path / "cert.pem" + certfile.write_text("cert", encoding="utf-8") + + runner = CliRunner() + result = runner.invoke( + cli, + [ + "dev", + "--config", + str(config_path), + "--ssl-certfile", + str(certfile), + ], + ) + + assert result.exit_code != 0 + assert "Both --ssl-certfile and --ssl-keyfile must be provided" in result.output + + def test_deploy_list_command(monkeypatch) -> None: runner = CliRunner() captured: dict[str, str] = {}