Skip to main content
Glama
OpenSIPS

OpenSIPS MCP Server

Official
by OpenSIPS

cfg_rollback

Restore a saved OpenSIPS configuration backup to a specified target path, automatically creating a backup of the current config for reversibility, then re-validate with opensips -C to confirm correctness.

Instructions

Restore backup_path to target_path and re-validate.

Before overwriting, the current contents of target_path (if any) are themselves backed up so this tool is always reversible one more step. If validate_after=True, the restored config is passed through opensips -C; a validation failure does NOT undo the restore (the caller asked for the restore explicitly), but the result is surfaced so the caller can decide what to do next.

Parameters

backup_path: Absolute path to the backup file to restore. target_path: Absolute path to overwrite. Must live in an allowlisted dir. validate_after: Run opensips -C on the restored file. Default True.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
backup_pathYes
target_pathYes
validate_afterNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • MCP tool handler for cfg_rollback. Restores a backup file to target_path with optional validation via opensips -C. Creates a pre-rollback snapshot before overwriting. Decorated with @mcp.tool(), @audited('cfg_rollback'), and @require_permission('config.write').
    @mcp.tool()
    @audited("cfg_rollback")
    @require_permission("config.write")
    async def cfg_rollback(
        ctx: Context,
        backup_path: str,
        target_path: str,
        validate_after: bool = True,
    ) -> dict[str, Any]:
        """Restore *backup_path* to *target_path* and re-validate.
    
        Before overwriting, the current contents of ``target_path`` (if any)
        are themselves backed up so this tool is always reversible one more
        step. If ``validate_after=True``, the restored config is passed
        through ``opensips -C``; a validation failure does NOT undo the
        restore (the caller asked for the restore explicitly), but the
        result is surfaced so the caller can decide what to do next.
    
        Parameters
        ----------
        backup_path:
            Absolute path to the backup file to restore.
        target_path:
            Absolute path to overwrite. Must live in an allowlisted dir.
        validate_after:
            Run ``opensips -C`` on the restored file. Default ``True``.
        """
        import datetime
        import shutil
    
        if not (os.path.isabs(backup_path) and os.path.isabs(target_path)):
            return {"error": "backup_path and target_path must both be absolute"}
        if not os.path.exists(backup_path):
            return {"error": f"backup_path does not exist: {backup_path}"}
        if not is_safe_out_dir(os.path.dirname(backup_path)):
            return {"error": "backup_path is outside the safe dir allowlist"}
        if not is_safe_out_dir(os.path.dirname(target_path)):
            return {"error": "target_path is outside the safe dir allowlist"}
    
        # Defensive: snapshot the current target so the rollback itself is
        # reversible. Ignore missing-file — that's fine, nothing to snapshot.
        pre_rollback_backup: str | None = None
        if os.path.exists(target_path):
            ts = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
            pre_rollback_backup = f"{target_path}.pre-rollback-{ts}"
            try:
                shutil.copy2(target_path, pre_rollback_backup)
            except OSError as exc:
                return {"error": f"pre-rollback snapshot failed: {exc}"}
    
        try:
            shutil.copy2(backup_path, target_path)
        except OSError as exc:
            return {"error": f"restore copy failed: {exc}"}
    
        out: dict[str, Any] = {
            "restored_from": backup_path,
            "restored_to": target_path,
            "pre_rollback_snapshot": pre_rollback_backup,
        }
        if validate_after:
            try:
                with open(target_path, encoding="utf-8") as fh:
                    content = fh.read()
                result = await _validator.validate(content)
                out["validation"] = {
                    "valid": result.valid,
                    "errors": result.errors,
                    "warnings": result.warnings,
                }
            except Exception as exc:
                out["validation"] = {"error": str(exc)}
        return out
  • The tool accepts backup_path (str), target_path (str), and validate_after (bool, default True). Returns a dict with restored_from, restored_to, pre_rollback_snapshot, and optional validation results.
    @mcp.tool()
    @audited("cfg_rollback")
    @require_permission("config.write")
    async def cfg_rollback(
        ctx: Context,
        backup_path: str,
        target_path: str,
        validate_after: bool = True,
    ) -> dict[str, Any]:
        """Restore *backup_path* to *target_path* and re-validate.
    
        Before overwriting, the current contents of ``target_path`` (if any)
        are themselves backed up so this tool is always reversible one more
        step. If ``validate_after=True``, the restored config is passed
        through ``opensips -C``; a validation failure does NOT undo the
        restore (the caller asked for the restore explicitly), but the
        result is surfaced so the caller can decide what to do next.
    
        Parameters
        ----------
        backup_path:
            Absolute path to the backup file to restore.
        target_path:
            Absolute path to overwrite. Must live in an allowlisted dir.
        validate_after:
            Run ``opensips -C`` on the restored file. Default ``True``.
        """
        import datetime
        import shutil
    
        if not (os.path.isabs(backup_path) and os.path.isabs(target_path)):
            return {"error": "backup_path and target_path must both be absolute"}
        if not os.path.exists(backup_path):
            return {"error": f"backup_path does not exist: {backup_path}"}
        if not is_safe_out_dir(os.path.dirname(backup_path)):
            return {"error": "backup_path is outside the safe dir allowlist"}
        if not is_safe_out_dir(os.path.dirname(target_path)):
            return {"error": "target_path is outside the safe dir allowlist"}
    
        # Defensive: snapshot the current target so the rollback itself is
        # reversible. Ignore missing-file — that's fine, nothing to snapshot.
        pre_rollback_backup: str | None = None
        if os.path.exists(target_path):
            ts = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
            pre_rollback_backup = f"{target_path}.pre-rollback-{ts}"
            try:
                shutil.copy2(target_path, pre_rollback_backup)
            except OSError as exc:
                return {"error": f"pre-rollback snapshot failed: {exc}"}
    
        try:
            shutil.copy2(backup_path, target_path)
        except OSError as exc:
            return {"error": f"restore copy failed: {exc}"}
    
        out: dict[str, Any] = {
            "restored_from": backup_path,
            "restored_to": target_path,
            "pre_rollback_snapshot": pre_rollback_backup,
        }
        if validate_after:
            try:
                with open(target_path, encoding="utf-8") as fh:
                    content = fh.read()
                result = await _validator.validate(content)
                out["validation"] = {
                    "valid": result.valid,
                    "errors": result.errors,
                    "warnings": result.warnings,
                }
            except Exception as exc:
                out["validation"] = {"error": str(exc)}
        return out
  • Registered as an MCP tool via @mcp.tool() decorator on line 1641, with @audited('cfg_rollback') and @require_permission('config.write') decorators.
    @mcp.tool()
    @audited("cfg_rollback")
    @require_permission("config.write")
    async def cfg_rollback(
  • CLI command registration for cfg_rollback as a Click command under the 'cfg' group. Provides a shell-callable interface that wraps similar logic.
    @cfg.command("rollback")
    @click.argument("backup_path", type=click.Path(exists=True, dir_okay=False))
    @click.argument("target_path", type=click.Path())
    @click.option("--no-validate", is_flag=True,
                  help="Skip post-rollback opensips -C validation.")
    def cfg_rollback(
        backup_path: str, target_path: str, no_validate: bool,
    ) -> None:
Behavior4/5

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

Despite no annotations, the description reveals key behaviors: automatic backup of current contents, validation behavior (including that validation failure does not undo the restore), and that results are surfaced. The only missing detail is potential side effects like permissions or locking.

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

Conciseness4/5

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

The description uses clear sections and bullet points, but the initial paragraph is dense. It could be shortened while retaining key information, but overall it is well-structured and not overly verbose.

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?

The description covers the tool's behavior and parameters but lacks details about the output schema (only mentions 'result is surfaced'). Given the tool has an output schema, the agent needs to know what the response contains (e.g., success status, validation errors) to act on the result.

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

Parameters5/5

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

With 0% schema coverage, the description fully explains all three parameters: backup_path (absolute path to backup), target_path (absolute path in allowlisted dir), validate_after (default True, runs opensips -C). This adds critical context beyond the schema's type-only definitions.

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 clearly states the tool's action ('Restore backup_path to target_path and re-validate') and distinguishes it from sibling config tools by noting the automatic backup before overwriting, making it uniquely reversible.

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 implies usage for config rollback but does not explicitly state when to use this tool over alternatives like cfg_backup or cfg_validate. No guidance on excluded scenarios or prerequisites beyond target path allowlisting.

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/OpenSIPS/opensips-mcp-server'

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