Skip to main content
Glama

apply_astrbot_config_ops

Apply batch edits to AstrBot configurations with set, add_key, and append operations, then automatically save and hot reload the updated config.

Instructions

Apply multiple edits to an AstrBot config, then save + hot reload.

Supported ops (batch in a single tool call):

  • {"op":"set","path":,"value":}

  • {"op":"add_key","path":<parent_path>,"key":,"value":}

  • {"op":"append","path":<list_path>,"value":}

path accepts dot path, JSON Pointer, or segment list.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
conf_idYes
opsYes
create_missingYes

Implementation Reference

  • The core handler function that fetches an AstrBot config by ID, applies batch operations (set, add_key, append) using flexible path parsing, updates the config via API, and returns success/error with changed paths.
    async def apply_astrbot_config_ops(
        *,
        conf_id: str,
        ops: List[Dict[str, Any]],
        create_missing: bool = False,
    ) -> Dict[str, Any]:
        """
        Apply multiple edits to an AstrBot config, then save + hot reload.
    
        Supported ops (batch in a single tool call):
          - {"op":"set","path":<path>,"value":<any>}
          - {"op":"add_key","path":<parent_path>,"key":<str>,"value":<any>}
          - {"op":"append","path":<list_path>,"value":<any>}
    
        `path` accepts dot path, JSON Pointer, or segment list.
        """
        client = AstrBotClient.from_env()
    
        if not conf_id:
            return {"status": "error", "message": "conf_id is required"}
        if not isinstance(ops, list) or not ops:
            return {"status": "error", "message": "ops must be a non-empty list"}
    
        try:
            api_result = await client.get_abconf(conf_id=conf_id, system_config=False)
        except Exception as e:
            return {
                "status": "error",
                "message": _astrbot_connect_hint(client),
                "base_url": client.base_url,
                "detail": _httpx_error_detail(e),
            }
    
        status = api_result.get("status")
        if status != "ok":
            return {"status": status, "message": api_result.get("message"), "raw": api_result}
    
        config = (api_result.get("data") or {}).get("config")
        if not isinstance(config, dict):
            return {"status": "error", "message": "AstrBot returned invalid config payload", "raw": api_result}
    
        changed: List[List[JsonPathSegment]] = []
        counters = {"set": 0, "add_key": 0, "append": 0}
        try:
            for i, op in enumerate(ops):
                if not isinstance(op, dict):
                    raise ValueError(f"ops[{i}] must be an object")
                op_name = (op.get("op") or "").strip()
                if op_name == "set":
                    path_segments = _parse_path(op.get("path"))
                    if not path_segments:
                        raise ValueError("set op requires non-empty path")
                    _set_value(
                        config,
                        path_segments,
                        op.get("value"),
                        create_missing=create_missing,
                    )
                    changed.append(path_segments)
                    counters["set"] += 1
                elif op_name == "add_key":
                    parent_segments = _parse_path(op.get("path"))
                    key = op.get("key")
                    if not isinstance(key, str) or not key.strip():
                        raise ValueError("add_key op requires non-empty string 'key'")
                    _add_key(
                        config,
                        parent_segments,
                        key=key,
                        value=op.get("value"),
                        create_missing=create_missing,
                    )
                    changed.append(parent_segments + [key])
                    counters["add_key"] += 1
                elif op_name == "append":
                    list_segments = _parse_path(op.get("path"))
                    _append_list_item(
                        config,
                        list_segments,
                        value=op.get("value"),
                        create_missing=create_missing,
                    )
                    changed.append(list_segments + ["-"])
                    counters["append"] += 1
                else:
                    raise ValueError(f"Unsupported op: {op_name!r}")
        except Exception as e:
            return {"status": "error", "message": str(e), "op_index": i, "op": ops[i]}
    
        try:
            update_result = await client.update_astrbot_config(conf_id=conf_id, config=config)
        except Exception as e:
            return {
                "status": "error",
                "message": f"AstrBot API error: {e.response.status_code if hasattr(e, 'response') else 'Unknown'}",
                "base_url": client.base_url,
                "detail": _httpx_error_detail(e),
            }
    
        status = update_result.get("status")
        if status != "ok":
            return {"status": status, "message": update_result.get("message"), "raw": update_result}
    
        return {
            "message": update_result.get("message") or "ok",
            "conf_id": conf_id,
            "applied": counters,
            "changed_paths": changed,
        }
  • Registers the 'apply_astrbot_config_ops' tool with the FastMCP server instance, making it available to MCP clients.
    server.tool(astrbot_tools.apply_astrbot_config_ops, name="apply_astrbot_config_ops")
  • Key helper function to parse input paths into JSONPath segments, supporting multiple formats (dot, JSON Pointer, lists). Used by the handler for all path-based operations.
    def _parse_path(path: str | List[Any] | None) -> List[JsonPathSegment]:
        """
        Parse a JSON path.
    
        Supported formats:
          - None / "" -> []
          - List segments: ["provider", 0, "model_config", "temperature"]
          - JSON Pointer: "/provider/0/model_config/temperature"
          - Dot path: "provider.0.model_config.temperature"
    
        Note:
          - JSON Pointer unescapes "~1" -> "/" and "~0" -> "~".
          - Numeric segments are converted to int.
          - "-" is kept as string (used for list append in some ops).
        """
        if path is None or path == "":
            return []
    
        if isinstance(path, list):
            segments: List[JsonPathSegment] = []
            for seg in path:
                if isinstance(seg, bool):
                    raise ValueError("Path segment cannot be bool")
                if isinstance(seg, int):
                    if seg < 0:
                        raise ValueError("List index cannot be negative")
                    segments.append(seg)
                    continue
                if isinstance(seg, str):
                    s = seg.strip()
                    if s == "":
                        raise ValueError("Path segment cannot be empty string")
                    if s.isdigit():
                        segments.append(int(s))
                    else:
                        segments.append(s)
                    continue
                raise ValueError(f"Unsupported path segment type: {type(seg).__name__}")
            return segments
    
        if not isinstance(path, str):
            raise ValueError(f"Unsupported path type: {type(path).__name__}")
    
        raw = path.strip()
        if raw == "":
            return []
    
        # JSON Pointer
        if raw.startswith("/"):
            parts = raw.split("/")[1:]
            out: List[JsonPathSegment] = []
            for part in parts:
                part = part.replace("~1", "/").replace("~0", "~")
                if part == "":
                    raise ValueError("Invalid JSON pointer: empty segment")
                if part != "-" and part.isdigit():
                    out.append(int(part))
                else:
                    out.append(part)
            return out
    
        # Dot path
        parts = raw.split(".")
        out = []
        for part in parts:
            part = part.strip()
            if part == "":
                raise ValueError("Invalid dot path: empty segment")
            if part.isdigit():
                out.append(int(part))
            else:
                out.append(part)
        return out
  • Re-exports 'apply_astrbot_config_ops' from config_tools.py in tools.__init__.py, allowing easy import as astrbot_tools.apply_astrbot_config_ops in server.py.
    "apply_astrbot_config_ops",
  • Also listed in the astrbot://info resource endpoint which advertises available tools to MCP hosts.
    server.tool(astrbot_tools.apply_astrbot_config_ops, name="apply_astrbot_config_ops")

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/xunxiing/astrbotmcp'

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