Skip to main content
Glama
gjenkins20

webmin-mcp-server

change_password

Change a user's password on a Webmin server. Provide username and new password. Blocked in safe mode to prevent unauthorized modifications.

Instructions

Change a user's password. This is a dangerous operation and is blocked in safe mode.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
serverNoServer alias (e.g., 'pi1', 'web-server'). Uses default server if not specified.
usernameYesUsername of the user
new_passwordYesNew password

Implementation Reference

  • Main handler function for the change_password tool. Implements password change logic: checks safe mode, validates password, finds user via passwd::find_user, and changes password via passwd::change_password.
    async def change_password(
        client: WebminClient,
        username: str,
        new_password: str,
        safe_mode: bool = True,
    ) -> ToolResult:
        """Change a user's password.
    
        This is a dangerous operation and is blocked in safe mode.
    
        Args:
            client: Authenticated WebminClient instance.
            username: Username of the user.
            new_password: New password.
            safe_mode: Whether safe mode is enabled.
    
        Returns:
            ToolResult with status.
        """
        # Safety check
        if safe_mode:
            return ToolResult.fail(
                code="SAFETY_BLOCKED",
                message="Changing passwords is blocked in safe mode. "
                "Disable safe mode (WEBMIN_SAFE_MODE=false) to allow password changes.",
            )
    
        # Validate password
        if not new_password or len(new_password) < 1:
            return ToolResult.fail(
                code="INVALID_PASSWORD",
                message="Password cannot be empty",
            )
    
        try:
            # Use passwd::find_user which searches both local and LDAP backends
            user = await client.call("passwd", "find_user", username)
    
            if not user:
                return ToolResult.fail(
                    code="USER_NOT_FOUND",
                    message=f"User '{username}' not found",
                )
    
            # Use passwd::change_password which properly encrypts the password,
            # updates shadow timestamps, handles file locking, runs pre/post hooks,
            # and propagates to other modules (Samba, MySQL, etc.)
            await client.call("passwd", "change_password", user, new_password, 1)
    
            return ToolResult.ok({
                "action": "change_password",
                "success": True,
                "username": username,
            })
    
        except Exception as e:
            return ToolResult.fail(
                code="CHANGE_PASSWORD_ERROR",
                message=f"Failed to change password for user '{username}': {e}",
            )
  • src/server.py:557-578 (registration)
    MCP Tool registration for 'change_password'. Defines the input schema with username and new_password required fields.
    Tool(
        name="change_password",
        description=(
            "Change a user's password. This is a dangerous operation and is "
            "blocked in safe mode."
        ),
        inputSchema={
            "type": "object",
            "properties": {
                **SERVER_PARAM,
                "username": {
                    "type": "string",
                    "description": "Username of the user",
                },
                "new_password": {
                    "type": "string",
                    "description": "New password",
                },
            },
            "required": ["username", "new_password"],
        },
    ),
  • Dispatch logic in call_tool that maps the 'change_password' tool name to the users.change_password handler function.
    if name == "change_password":
        username = arguments.get("username")
        new_password = arguments.get("new_password")
        if not username:
            return ToolResult.fail(
                code="MISSING_ARGUMENT",
                message="Missing required argument: username",
            )
        if not new_password:
            return ToolResult.fail(
                code="MISSING_ARGUMENT",
                message="Missing required argument: new_password",
            )
        return await users.change_password(
            client,
            username=username,
            new_password=new_password,
            safe_mode=config.safe_mode,
        )
  • ToolResult model - the return type used by change_password to return success/failure responses.
    class ToolResult(BaseModel):
        """Generic result wrapper for MCP tool responses."""
    
        success: bool = Field(
            description="Whether the operation succeeded",
        )
        data: dict[str, Any] | None = Field(
            default=None,
            description="Result data on success",
        )
        error: WebminError | None = Field(
            default=None,
            description="Error details on failure",
        )
    
        @classmethod
        def ok(cls, data: dict[str, Any]) -> "ToolResult":
            """Create a successful result."""
            return cls(success=True, data=data)
    
        @classmethod
        def fail(
            cls,
            code: str,
            message: str,
            details: dict[str, Any] | None = None,
        ) -> "ToolResult":
            """Create a failure result."""
            return cls(
                success=False,
                error=WebminError(code=code, message=message, details=details),
            )
  • Helper function _validate_username used by other user tools but not directly by change_password (which skips username validation).
    def _validate_username(username: str) -> str | None:
        """Validate a username.
    
        Args:
            username: The username to validate.
    
        Returns:
            Error message if invalid, None if valid.
        """
        if not username:
            return "Username cannot be empty"
    
        if len(username) > 32:
            return "Username must be 32 characters or less"
    
        # Linux username rules: lowercase letters, digits, underscore, hyphen
        # Must start with a letter or underscore
        if not re.match(r"^[a-z_][a-z0-9_-]*$", username):
            return (
                "Username must start with a letter or underscore, and contain only "
                "lowercase letters, digits, underscores, and hyphens"
            )
    
        return None
Behavior3/5

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

No annotations are provided, so the description carries the transparency burden. It discloses the operation is dangerous and blocked in safe mode, but lacks details on privilege requirements, irreversibility, or potential side effects. Adequate but minimal.

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?

Two concise sentences, no extraneous words, front-loads the action. Every sentence adds value.

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

Completeness3/5

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

Given no output schema, the description could indicate return behavior or success/failure signals. It only notes the danger, leaving the agent to infer response format. Sufficient for a simple operation but not comprehensive.

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?

The input schema covers 100% of parameters with good descriptions. The tool description adds no additional meaning beyond the schema, so a baseline of 3 is appropriate.

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

Purpose5/5

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

The description uses a specific verb ('Change') and resource ('a user's password'), clearly distinguishing from sibling tools like create_user or modify_user. The additional note about being dangerous and blocked in safe mode adds clarity.

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

Usage Guidelines3/5

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

The description mentions it is blocked in safe mode, providing some context, but does not explicitly guide when to use this tool versus alternatives like modify_user, which might also handle password changes. No when-not-to-use guidance is given.

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/gjenkins20/webmin-mcp-server'

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