Ctrl K
ComunidadGuías…

butler/mcp/schema_normalize.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Normalize MCP / registry tool schemas (double-wrap protection)."""

from __future__ import annotations

from copy import deepcopy
from typing import Any


def _is_openai_function_shell(obj: Any) -> bool:
    if not isinstance(obj, dict):
        return False
    if str(obj.get("type") or "") != "function":
        return False
    fn = obj.get("function")
    if not isinstance(fn, dict):
        return False
    if fn.get("name"):
        return True
    inner = fn.get("function")
    return isinstance(inner, dict) and bool(inner.get("name"))


def normalize_tool_schema(schema: dict[str, Any]) -> dict[str, Any]:
    """Flatten double-wrapped OpenAI function schemas."""
    if not isinstance(schema, dict):
        return schema
    out = deepcopy(schema)
    while _is_openai_function_shell(out):
        fn = out.get("function")
        if not isinstance(fn, dict):
            break
        if fn.get("name"):
            out = {"type": "function", "function": _normalize_function_block(fn)}
            break
        inner = fn.get("function")
        if isinstance(inner, dict) and inner.get("name"):
            out = {"type": "function", "function": _normalize_function_block(inner)}
            break
        break
    if "name" in out and "parameters" in out and "type" not in out:
        return {
            "type": "function",
            "function": {
                "name": out.get("name"),
                "description": out.get("description", ""),
                "parameters": _normalize_parameters(out.get("parameters")),
            },
        }
    return out


def _normalize_function_block(fn: dict[str, Any]) -> dict[str, Any]:
    block = dict(fn)
    block["parameters"] = _normalize_parameters(block.get("parameters"))
    return block


def _normalize_parameters(params: Any) -> dict[str, Any]:
    if not isinstance(params, dict):
        return {"type": "object", "properties": {}}
    if _is_openai_function_shell(params):
        inner = params.get("function")
        if isinstance(inner, dict):
            return _normalize_parameters(inner.get("parameters"))
    p = dict(params)
    if "type" not in p:
        p["type"] = "object"
    if "properties" not in p:
        p["properties"] = {}
    return p


def normalize_tool_definitions(definitions: list[dict[str, Any]]) -> list[dict[str, Any]]:
    return [normalize_tool_schema(d) for d in definitions if isinstance(d, dict)]


__all__ = ["normalize_tool_definitions", "normalize_tool_schema"]