start_container
Detects the active plugin (whale, ctfd-owl, or k8s) and starts a container for the specified challenge ID, enabling dynamic competition environments.
Instructions
Unified start: detects plugin (whale/ctfd-owl/k8s) and starts container.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| challenge_id | Yes |
Implementation Reference
- src/ctfd_mcp/server.py:102-110 (handler)MCP tool endpoint for start_container - the async function decorated with @mcp.tool that receives challenge_id and calls client.start_container(challenge_id).
@mcp.tool( description="Unified start: detects plugin (whale/ctfd-owl/k8s) and starts container." ) async def start_container(challenge_id: int): client = await _get_client() try: return await client.start_container(challenge_id) except Exception as exc: # noqa: BLE001 raise _format_error(exc) - src/ctfd_mcp/ctfd_client.py:558-579 (handler)Unified start_container implementation on CTFdClient - detects the challenge type (dynamic_docker, dynamic_check_docker, k8s) and dispatches to the appropriate backend handler (start_k8s_container, start_dynamic_container, start_owl_container).
async def start_container(self, challenge_id: int) -> dict[str, Any]: """ Unified start: detects challenge type and calls the appropriate backend. - dynamic_docker -> ctfd-whale /api/v1/containers - dynamic_check_docker -> ctfd-owl /plugins/ctfd-owl/container - k8s-backed -> /api/v1/k8s (form-based) """ details = await self.get_challenge(challenge_id) ctype = (details.get("type") or "").lower() if self._is_k8s_type(ctype): return await self.start_k8s_container(challenge_id) if ctype == "dynamic_docker": try: return await self.start_dynamic_container(challenge_id) except NotFoundError: # Some events expose dynamic challenges via /api/v1/k8s while keeping the same type. return await self.start_k8s_container(challenge_id) if ctype == "dynamic_check_docker": return await self.start_owl_container(challenge_id) raise CTFdClientError( f"Unsupported challenge type '{ctype}' for container start." ) - src/ctfd_mcp/ctfd_client.py:245-258 (schema)Schema/return shape for dynamic_docker container start (start_dynamic_container) - returns connection_info, ip, port, host, container_id, etc.
return self._trim_none( { "id": data.get("id"), "challenge_id": data.get("challenge_id"), "state": data.get("state"), "connection_info": connection_info, "ip": data.get("ip"), "port": port, "host": host, "container_id": data.get("container_id"), "created": data.get("created"), "raw": data, } ) - src/ctfd_mcp/ctfd_client.py:343-354 (schema)Schema/return shape for k8s container start (start_k8s_container) - returns connection_info, instance_running, expires_at, etc.
return { "challenge_id": challenge_id, "connection_info": connection_info, "connection_url": connection_url, "connection_port": port, "expires_at": expires_at, "instance_running": data.get("InstanceRunning"), "is_current_instance": data.get("ThisChallengeInstance"), "extend_available": data.get("ExtendAvailable"), "state": state, "raw": data, } - src/ctfd_mcp/ctfd_client.py:491-506 (schema)Schema/return shape for ctfd-owl container start (start_owl_container) - returns connection_info, ip, port, remaining_time, container_id, etc.
return { "id": data.get("id") or container_info.get("id"), "challenge_id": data.get("challenge_id") or challenge_id, "state": data.get("state") or container_info.get("state"), "connection_info": connection_info, "ip": ip, "port": port, "host": host, "conntype": conntype, "remaining_time": container_info.get("remaining_time") or data.get("remaining_time"), "container_id": container_info.get("container_id") or data.get("container_id"), "created": data.get("created") or container_info.get("created"), "raw": data, } - src/ctfd_mcp/ctfd_client.py:522-527 (helper)Helper method _is_k8s_type used by start_container to detect k8s-backed challenges.
@staticmethod def _is_k8s_type(ctype: str | None) -> bool: if not ctype: return False lowered = ctype.lower() return "k8s" in lowered or "kube" in lowered - src/ctfd_mcp/ctfd_client.py:529-532 (helper)Helper method _trim_none used by start_container to remove None values from responses.
@staticmethod def _trim_none(values: dict[str, Any]) -> dict[str, Any]: """Drop keys with None to keep responses compact.""" return {k: v for k, v in values.items() if v is not None} - src/ctfd_mcp/server.py:102-108 (registration)Registration of start_container as an MCP tool via @mcp.tool decorator with description.
@mcp.tool( description="Unified start: detects plugin (whale/ctfd-owl/k8s) and starts container." ) async def start_container(challenge_id: int): client = await _get_client() try: return await client.start_container(challenge_id)