Skip to main content
Glama
samerfarida

MCP SSH Orchestrator

ssh_cancel

Cancel a running SSH task to stop execution immediately.

Instructions

Request cancellation for a running task.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
task_idNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • The `ssh_cancel` tool handler function. Validates the task_id, calls TASKS.cancel() to signal cancellation on the synchronous TaskManager, and returns a JSON response indicating success or failure.
    @mcp.tool()
    def ssh_cancel(task_id: str = "", ctx: Context | None = None) -> ToolResult:
        """Request cancellation for a running task."""
        try:
            # Input validation
            valid, error_msg = _validate_task_id(task_id)
            if not valid:
                return f"Error: {error_msg}"
    
            task_id = task_id.strip()
            ok = TASKS.cancel(task_id)
            response = {
                "task_id": task_id,
                "cancelled": bool(ok),
                "message": "Cancellation signaled" if ok else "Task not found",
            }
            _ctx_log(
                ctx,
                "info",
                "ssh_cancel",
                {"task_id": task_id, "cancelled": bool(ok)},
            )
            return response
        except Exception as e:
            error_str = str(e)
            log_json({"level": "error", "msg": "cancel_exception", "error": error_str})
            _ctx_log(
                ctx,
                "debug",
                "ssh_cancel_error",
                {"task_id": task_id.strip(), "error": sanitize_error(error_str)},
            )
            return f"Cancel error: {sanitize_error(error_str)}"
  • The `@mcp.tool()` decorator registers `ssh_cancel` as an MCP tool.
    @mcp.tool()
    def ssh_cancel(task_id: str = "", ctx: Context | None = None) -> ToolResult:
        """Request cancellation for a running task."""
  • Validation of the `task_id` input parameter used by `ssh_cancel`, enforcing format, length, and character restrictions.
    def _validate_task_id(task_id: str) -> tuple[bool, str]:
        """Validate task_id parameter.
    
        Security: Validates task_id format.
        - Length limit: 200 characters
        - Format validation (expected: alias:hash:timestamp)
        - Cannot be empty
    
        Args:
            task_id: Task ID string to validate
    
        Returns:
            Tuple of (is_valid, error_message)
            If valid: (True, "")
            If invalid: (False, error_message)
        """
        if not task_id or not task_id.strip():
            return False, "task_id is required"
    
        task_id = task_id.strip()
    
        # Length validation
        if len(task_id) > MAX_TASK_ID_LENGTH:
            return False, f"task_id too long (max {MAX_TASK_ID_LENGTH} characters)"
    
        # Format validation: should match pattern alias:hash:timestamp
        # Allow alphanumeric, colon, dash, underscore
        if not re.match(r"^[a-zA-Z0-9:_-]+$", task_id):
            return (
                False,
                "task_id contains invalid characters (only alphanumeric, colon, dash, underscore allowed)",
            )
    
        return True, ""
  • The `TaskManager` class (aliased as `TASKS`) provides the `cancel()` method that actually signals cancellation by setting a threading.Event. The `ssh_cancel` handler delegates to `TASKS.cancel(task_id)`.
    class TaskManager:
        """In-memory task registry for cancellation."""
    
        def __init__(self):
            self._lock = threading.Lock()
            self._tasks = {}
    
        def create(self, alias: str, command_hash: str) -> str:
            """Create task and return id."""
            with self._lock:
                # Use microsecond precision to avoid collisions
                timestamp = int(time.time() * 1000000)
                task_id = f"{alias}:{command_hash}:{timestamp}"
                self._tasks[task_id] = {
                    "cancel": threading.Event(),
                    "created": time.time(),
                    "alias": alias,
                    "hash": command_hash,
                }
                return task_id
    
        def cancel(self, task_id: str) -> bool:
            """Signal cancellation for task id."""
            with self._lock:
                if task_id in self._tasks:
                    self._tasks[task_id]["cancel"].set()
                    return True
                return False
    
        def get_event(self, task_id: str):
            """Return cancel event for task id."""
            with self._lock:
                t = self._tasks.get(task_id)
                if t:
                    return t["cancel"]
                return None
    
        def cleanup(self, task_id: str):
            """Remove task."""
            with self._lock:
                if task_id in self._tasks:
                    del self._tasks[task_id]
  • Tests for `ssh_cancel`: verifies behavior when task_id not found and when task_id is empty.
    def test_ssh_cancel_not_found():
        """Test cancel tool with non-existent task."""
        result = mcp_server.ssh_cancel(task_id="nonexistent")
        assert isinstance(result, dict)
        assert result.get("cancelled") is False
        assert "not found" in result.get("message", "").lower()
    
    
    def test_ssh_cancel_no_task_id():
        """Test cancel tool without task_id."""
        result = mcp_server.ssh_cancel(task_id="")
        # Error case - still returns string
        assert isinstance(result, str) or (
            isinstance(result, dict) and "error" in str(result).lower()
        )
        assert "required" in str(result).lower()
Behavior2/5

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

With no annotations, the description must disclose behavioral traits. It implies a cancellation request is sent but does not explain if the task is immediately cancelled, if it's asynchronous, or what the return value indicates. Important side effects or permissions are omitted.

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

Conciseness3/5

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

The description is a single sentence, which is concise but lacks any structure. It is not verbose, but the brevity comes at the cost of missing crucial details.

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?

Given the simplicity (1 parameter, output schema exists), the description is incomplete. It does not explain cancellation behavior, expected output, or prerequisites. More context is needed for effective tool usage.

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

Parameters1/5

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

The only parameter 'task_id' has no schema description (0% coverage). The tool description does not explain what a task ID is, how to obtain it, or its required format. The description fails to add meaning beyond the schema.

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 'Request cancellation for a running task' clearly states the verb and resource. However, it does not differentiate from the sibling tool 'ssh_cancel_async_task', which may have similar functionality.

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 is provided on when to use this tool versus alternatives like 'ssh_cancel_async_task' or other ssh tools. The description gives no context for appropriate usage scenarios.

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/samerfarida/mcp-ssh-orchestrator'

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