From 44660d18ceb220600401303df860a31ca766c817 Mon Sep 17 00:00:00 2001 From: Eric Gustin <34000337+EricGustin@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:39:14 -0800 Subject: [PATCH] Only serve worker endpoints if secret is set (#691) Default to `ARCADE_WORKER_SECRET` being unset. This env var must be explicitly set now. Once it is set, the `worker/` endpoints will be served. --- libs/arcade-cli/arcade_cli/deploy.py | 1 + .../arcade_mcp_server/settings.py | 4 +-- .../arcade_mcp_server/worker.py | 21 ++++++++-------- libs/arcade-mcp-server/pyproject.toml | 2 +- .../integration/test_end_to_end.py | 1 + libs/tests/worker/test_worker_base.py | 25 ++++++++++++++++--- pyproject.toml | 2 +- 7 files changed, 38 insertions(+), 18 deletions(-) diff --git a/libs/arcade-cli/arcade_cli/deploy.py b/libs/arcade-cli/arcade_cli/deploy.py index 842dee35..927701a3 100644 --- a/libs/arcade-cli/arcade_cli/deploy.py +++ b/libs/arcade-cli/arcade_cli/deploy.py @@ -383,6 +383,7 @@ def start_server_process(entrypoint: str, debug: bool = False) -> tuple[subproce "ARCADE_SERVER_PORT": str(port), "ARCADE_SERVER_TRANSPORT": "http", "ARCADE_AUTH_DISABLED": "true", + "ARCADE_WORKER_SECRET": "temp-validation-secret", } cmd = [sys.executable, entrypoint] diff --git a/libs/arcade-mcp-server/arcade_mcp_server/settings.py b/libs/arcade-mcp-server/arcade_mcp_server/settings.py index 21dde7ed..942dcefb 100644 --- a/libs/arcade-mcp-server/arcade_mcp_server/settings.py +++ b/libs/arcade-mcp-server/arcade_mcp_server/settings.py @@ -142,8 +142,8 @@ class ArcadeSettings(BaseSettings): description="Disable authentication", ) server_secret: str | None = Field( - default="dev", - description="Server secret", + default=None, + description="Server secret for worker endpoints (required to enable worker routes)", validation_alias="ARCADE_WORKER_SECRET", ) environment: str = Field( diff --git a/libs/arcade-mcp-server/arcade_mcp_server/worker.py b/libs/arcade-mcp-server/arcade_mcp_server/worker.py index 6db9764b..633b2a46 100644 --- a/libs/arcade-mcp-server/arcade_mcp_server/worker.py +++ b/libs/arcade-mcp-server/arcade_mcp_server/worker.py @@ -123,15 +123,14 @@ def create_arcade_mcp( **kwargs: Any, ) -> FastAPI: """ - Create a FastAPI app exposing Arcade Worker and MCP HTTP endpoints. + Create a FastAPI app exposing MCP HTTP endpoints + and Arcade Worker endpoints if a secret is provided. MCP is always enabled in this integrated application. """ if mcp_settings is None: mcp_settings = MCPSettings.from_env() secret = mcp_settings.arcade.server_secret - if secret is None: - secret = "dev" # noqa: S105 otel_handler = OTELHandler( enable=otel_enable, @@ -180,13 +179,15 @@ def create_arcade_mcp( app.add_middleware(AddTrailingSlashToPathMiddleware) # Worker endpoints - worker = FastAPIWorker( - app=app, - secret=secret, - disable_auth=mcp_settings.arcade.auth_disabled, - otel_meter=otel_handler.get_meter(), - ) - worker.catalog = catalog + if secret is not None: + worker = FastAPIWorker( + app=app, + secret=secret, + disable_auth=mcp_settings.arcade.auth_disabled, + otel_meter=otel_handler.get_meter(), + ) + worker.catalog = catalog + logger.info("Worker routes enabled at /worker/* (ARCADE_WORKER_SECRET is set)") class _MCPASGIProxy: def __init__(self, parent_app: FastAPI): diff --git a/libs/arcade-mcp-server/pyproject.toml b/libs/arcade-mcp-server/pyproject.toml index 3ef84b5e..23851845 100644 --- a/libs/arcade-mcp-server/pyproject.toml +++ b/libs/arcade-mcp-server/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "arcade-mcp-server" -version = "1.8.0" +version = "1.9.0" description = "Model Context Protocol (MCP) server framework for Arcade.dev" readme = "README.md" authors = [{ name = "Arcade.dev" }] diff --git a/libs/tests/arcade_mcp_server/integration/test_end_to_end.py b/libs/tests/arcade_mcp_server/integration/test_end_to_end.py index be716ed2..d18e21fa 100644 --- a/libs/tests/arcade_mcp_server/integration/test_end_to_end.py +++ b/libs/tests/arcade_mcp_server/integration/test_end_to_end.py @@ -64,6 +64,7 @@ def start_mcp_server( "ARCADE_SERVER_PORT": str(port), "ARCADE_SERVER_TRANSPORT": "http", "ARCADE_AUTH_DISABLED": "true", + "ARCADE_WORKER_SECRET": "test-secret-e2e", } cmd = ["uv", "run", entrypoint_path, "http"] diff --git a/libs/tests/worker/test_worker_base.py b/libs/tests/worker/test_worker_base.py index 3f3708fd..537d85fc 100644 --- a/libs/tests/worker/test_worker_base.py +++ b/libs/tests/worker/test_worker_base.py @@ -44,12 +44,17 @@ def mock_router(): @pytest.fixture def base_worker(mock_router): + # Save original value + original_secret = os.environ.get("ARCADE_WORKER_SECRET") # Set env var temporarily for testing secret loading os.environ["ARCADE_WORKER_SECRET"] = "test_secret_env" # noqa: S105 worker = BaseWorker() worker.register_routes(mock_router) # Register routes using the mock router - # Clean up env var - del os.environ["ARCADE_WORKER_SECRET"] + # Restore original value + if original_secret is not None: + os.environ["ARCADE_WORKER_SECRET"] = original_secret + else: + del os.environ["ARCADE_WORKER_SECRET"] return worker @@ -68,20 +73,32 @@ def test_base_worker_init_with_secret(): def test_base_worker_init_with_env_secret(): + original_secret = os.environ.get("ARCADE_WORKER_SECRET") os.environ["ARCADE_WORKER_SECRET"] = "env_secret_value" # noqa: S105 worker = BaseWorker() assert worker.secret == "env_secret_value" # noqa: S105 assert not worker.disable_auth - del os.environ["ARCADE_WORKER_SECRET"] + + # Restore secret to original if it was set + if original_secret is not None: + os.environ["ARCADE_WORKER_SECRET"] = original_secret + else: + del os.environ["ARCADE_WORKER_SECRET"] def test_base_worker_init_no_secret_raises_error(): - # Ensure env var is not set + # Ensure secret is not set + original_secret = os.environ.get("ARCADE_WORKER_SECRET") if "ARCADE_WORKER_SECRET" in os.environ: del os.environ["ARCADE_WORKER_SECRET"] + with pytest.raises(ValueError, match="No secret provided for worker"): BaseWorker() + # Restore secret if it was set + if original_secret is not None: + os.environ["ARCADE_WORKER_SECRET"] = original_secret + def test_base_worker_init_disable_auth(): worker = BaseWorker(disable_auth=True) diff --git a/pyproject.toml b/pyproject.toml index deaa7fdd..405ea72d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "arcade-mcp" -version = "1.5.3" +version = "1.5.4" description = "Arcade.dev - Tool Calling platform for Agents" readme = "README.md" license = {file = "LICENSE"}