Skip to content

Schema

schema

IntrospectableFn

Bases: Protocol

Structural protocol for any callable that supports inspect.signature.

Source code in packages/axm-mcp/src/axm_mcp/schema.py
Python
class IntrospectableFn(Protocol):
    """Structural protocol for any callable that supports ``inspect.signature``."""

    __doc__: str | None

    def __call__(self, **kwargs: object) -> object: ...

apply_signature(wrapper, exec_fn, override_module)

Set the typed __signature__ so FastMCP builds the right schema.

Source code in packages/axm-mcp/src/axm_mcp/schema.py
Python
def apply_signature(
    wrapper: _SignatureTarget,
    exec_fn: IntrospectableFn,
    override_module: ModuleType | None,
) -> None:
    """Set the typed ``__signature__`` so FastMCP builds the right schema."""
    try:
        union_params = collect_dispatcher_params(
            exec_fn, override_module=override_module
        )
        params = union_params if union_params is not None else signature_params(exec_fn)
        wrapper.__signature__ = inspect.Signature(  # type: ignore[attr-defined]
            parameters=params,
            return_annotation=dict[str, object] | str,
        )
    except (ValueError, TypeError):
        pass  # Fall back to generic **kwargs if introspection fails

collect_dispatcher_params(fn, *, override_module=None)

Collect union of typed params from dispatcher sub-functions.

A dispatcher is a function with action: str + **kwargs that routes to sub-functions stored in a module-level _*_ACTIONS dict. This helper introspects all sub-functions and returns the union of their parameters (all made optional).

Parameters:

Name Type Description Default
fn IntrospectableFn

The dispatcher function to introspect.

required
override_module ModuleType | None

Module to search for _*_ACTIONS dict. If None, uses inspect.getmodule(fn).

None

Returns:

Type Description
list[Parameter] | None

List of inspect.Parameter if fn is a dispatcher, else None.

Source code in packages/axm-mcp/src/axm_mcp/schema.py
Python
def collect_dispatcher_params(
    fn: IntrospectableFn,
    *,
    override_module: ModuleType | None = None,
) -> list[inspect.Parameter] | None:
    """Collect union of typed params from dispatcher sub-functions.

    A *dispatcher* is a function with ``action: str`` + ``**kwargs``
    that routes to sub-functions stored in a module-level ``_*_ACTIONS``
    dict.  This helper introspects all sub-functions and returns the
    union of their parameters (all made optional).

    Args:
        fn: The dispatcher function to introspect.
        override_module: Module to search for ``_*_ACTIONS`` dict.
            If *None*, uses ``inspect.getmodule(fn)``.

    Returns:
        List of ``inspect.Parameter`` if *fn* is a dispatcher, else *None*.
    """
    sig = _safe_signature(fn)
    if sig is None:
        return None

    params = list(sig.parameters.values())

    # Detect dispatcher pattern: has 'action' param + VAR_KEYWORD
    has_action = any(p.name == "action" for p in params)
    has_varkw = any(p.kind == inspect.Parameter.VAR_KEYWORD for p in params)
    if not (has_action and has_varkw):
        return None

    # Find the _ACTIONS dict by convention
    module = override_module or inspect.getmodule(fn)
    actions_dict = _find_actions_dict(module)
    if not actions_dict:
        return None

    # Collect + build final: action (required) + sub-fn params (optional)
    seen = _union_subfn_params(actions_dict)
    action_param = next(p for p in params if p.name == "action")
    return [action_param, *sorted(seen.values(), key=lambda p: p.name)]

extract_docstring_params(docstring)

Parse Google-style Args: section into inspect.Parameter objects.

This is the fallback for non-dispatcher tools whose execute(**kwargs) has no typed parameters in the signature but documents them in the docstring.

Parameters:

Name Type Description Default
docstring str | None

The docstring to parse.

required

Returns:

Type Description
list[Parameter]

List of keyword-only inspect.Parameter with default None.

list[Parameter]

Empty list if no Args: section found.

Source code in packages/axm-mcp/src/axm_mcp/schema.py
Python
def extract_docstring_params(
    docstring: str | None,
) -> list[inspect.Parameter]:
    """Parse Google-style ``Args:`` section into ``inspect.Parameter`` objects.

    This is the fallback for non-dispatcher tools whose ``execute(**kwargs)``
    has no typed parameters in the signature but documents them in the
    docstring.

    Args:
        docstring: The docstring to parse.

    Returns:
        List of keyword-only ``inspect.Parameter`` with default ``None``.
        Empty list if no ``Args:`` section found.
    """
    if not docstring:
        return []

    in_args = False
    params: list[inspect.Parameter] = []

    for line in docstring.splitlines():
        stripped = line.strip()

        if stripped in ("Args:", "Keyword Args:"):
            in_args = True
            continue

        if not in_args:
            continue

        if _is_section_end(stripped):
            break

        if stripped.startswith("**"):
            continue

        param = _parse_arg_line(line)
        if param is not None:
            params.append(param)

    return params

signature_params(exec_fn)

Strip self / **kwargs from exec_fn, falling back to its docstring.

Source code in packages/axm-mcp/src/axm_mcp/schema.py
Python
def signature_params(exec_fn: IntrospectableFn) -> list[inspect.Parameter]:
    """Strip ``self`` / ``**kwargs`` from *exec_fn*, falling back to its docstring."""
    try:
        exec_sig = inspect.signature(exec_fn, eval_str=True)
    except (ValueError, TypeError, NameError):
        exec_sig = inspect.signature(exec_fn)
    params = [
        p
        for p in exec_sig.parameters.values()
        if p.name != "self" and p.kind != inspect.Parameter.VAR_KEYWORD
    ]
    return params or extract_docstring_params(exec_fn.__doc__)