Skip to main content
Glama
umbra2728

CTFd MCP Server

stop_container

Stop a running CTFd container by providing its container ID or the challenge ID. Compatible with whale, ctfd-owl, and k8s backends.

Instructions

Unified stop: whale requires container_id; ctfd-owl/k8s require challenge_id.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
container_idNo
challenge_idNo

Implementation Reference

  • Main stop_container handler: dispatches to stop_dynamic_container, stop_k8s_container, or stop_owl_container based on challenge type. Requires container_id for dynamic_docker, challenge_id for k8s or ctfd-owl.
    async def stop_container(
        self, *, container_id: int | None = None, challenge_id: int | None = None
    ) -> dict[str, Any]:
        """
        Unified stop:
        - dynamic_docker: requires container_id
        - dynamic_check_docker (owl): uses challenge_id
        - k8s-backed: uses challenge_id
        If no challenge_id is given, assumes dynamic_docker and stops by container_id.
        """
        if container_id is None and challenge_id is None:
            raise CTFdClientError(
                "Provide container_id or challenge_id to stop a container."
            )
    
        ctype: str | None = None
        if challenge_id is not None:
            details = await self.get_challenge(challenge_id)
            ctype = (details.get("type") or "").lower()
    
        if self._is_k8s_type(ctype):
            if challenge_id is None:
                raise CTFdClientError("k8s stop requires challenge_id.")
            return await self.stop_k8s_container(challenge_id)
        if ctype == "dynamic_docker":
            if container_id is None:
                raise CTFdClientError("dynamic_docker stop requires container_id.")
            try:
                return await self.stop_dynamic_container(container_id)
            except NotFoundError:
                if challenge_id is not None:
                    return await self.stop_k8s_container(challenge_id)
                raise
        if ctype == "dynamic_check_docker":
            if challenge_id is None:
                raise CTFdClientError("ctfd-owl stop requires challenge_id.")
            return await self.stop_owl_container(challenge_id)
    
        if ctype:
            raise CTFdClientError(
                f"Unsupported challenge type '{ctype}' for container stop."
            )
    
        # No challenge_id was provided; default to whale-style stop by container_id.
        if container_id is None:
            raise CTFdClientError("dynamic_docker stop requires container_id.")
        return await self.stop_dynamic_container(container_id)
  • Helper: stop_dynamic_container - sends DELETE /api/v1/containers/{container_id}
    async def stop_dynamic_container(self, container_id: int) -> dict[str, Any]:
        """Stop a dynamic_docker instance."""
        payload = await self._request(
            "DELETE",
            f"/api/v1/containers/{container_id}",
        )
        data = payload.get("data") or payload or {}
        message = data.get("message") or data or "stopped"
        if isinstance(message, dict):
            message = message.get("message") or str(message)
        return self._trim_none(
            {
                "status": data.get("status") or payload.get("success"),
                "message": message,
                "raw": data,
            }
        )
  • Helper: stop_k8s_container - sends POST /api/v1/k8s/delete with CSRF nonce
    async def stop_k8s_container(self, challenge_id: int) -> dict[str, Any]:
        """
        Stop a Kubernetes-backed instance exposed via /api/v1/k8s endpoints.
        """
        files = {"challenge_id": (None, str(challenge_id))}
        if self._csrf_token:
            files["nonce"] = (None, self._csrf_token)
    
        await self._k8s_request(
            "POST", "/api/v1/k8s/delete", files=files, action="stop"
        )
        status = await self._get_k8s_container(challenge_id)
        status["status"] = (
            "stopped" if not status.get("instance_running") else "running"
        )
        return self._trim_none(status)
  • Helper: stop_owl_container - sends DELETE /plugins/ctfd-owl/container with challenge_id
    async def stop_owl_container(self, challenge_id: int) -> dict[str, Any]:
        """
        Stop a dynamic_check_docker (ctfd-owl) instance.
        Uses the same endpoint with DELETE and challenge_id.
        """
        headers = self._owl_headers()
        payload = await self._request(
            "DELETE",
            "/plugins/ctfd-owl/container",
            params={"challenge_id": challenge_id},
            headers=headers,
        )
        data = payload.get("data") or payload
        message = data.get("message") or data or "stopped"
        if isinstance(message, dict):
            message = message.get("message") or str(message)
        return self._trim_none(
            {
                "status": data.get("status") or payload.get("success"),
                "message": message,
                "raw": data,
            }
  • MCP tool registration: @mcp.tool decorator registers stop_container as an MCP tool with description
    @mcp.tool(
        description="Unified stop: whale requires container_id; ctfd-owl/k8s require challenge_id."
    )
    async def stop_container(
        container_id: int | None = None, challenge_id: int | None = None
    ):
        client = await _get_client()
        try:
            return await client.stop_container(
                container_id=container_id, challenge_id=challenge_id
            )
        except Exception as exc:  # noqa: BLE001
            raise _format_error(exc)
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations provided, so the description must carry behavioral disclosure. It fails to state whether stopping is destructive, requires authentication, or what the effect is. The mention of different systems hints at behavior but lacks detail.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Single sentence with no extraneous words. Front-loaded with the action 'Unified stop'. Every word serves a purpose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Lacks details on return values, errors, prerequisites, or side effects. No output schema and minimal description make it insufficient for a complete understanding.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage, the description adds partial meaning by linking parameters to systems (whale vs. ctfd-owl/k8s). However, it doesn't explain the parameters fully (e.g., format, requiredness beyond default null).

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states 'Unified stop', clearly indicating the tool stops containers. It differentiates from sibling 'start_container' by being the inverse operation. However, it does not specify a single verb+resource but rather describes two possible use cases.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance on when to use this tool versus alternatives like 'start_container' or when not to use it. The only usage hint is about choosing parameters based on system, which is implicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/umbra2728/ctfd-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server