Coverage for mcp_chatkit_widget / tooling.py: 100%
33 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 06:37 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-11 06:37 +0000
1"""Tooling helpers that expose FastMCP registration utilities."""
3from __future__ import annotations
4import inspect
5from collections.abc import Callable
6from typing import Any, cast
7from chatkit.widgets import WidgetComponentBase
8from fastmcp import FastMCP
9from mcp_chatkit_widget.naming import _sanitize_tool_name, _to_camel_case
10from mcp_chatkit_widget.rendering import build_widget_model, render_widget_definition
11from mcp_chatkit_widget.widget_loader import WidgetDefinition
14def _create_widget_tool_function(
15 widget_def: WidgetDefinition,
16) -> Callable[..., WidgetComponentBase]:
17 """Create a FastMCP tool function for a given widget definition."""
18 pydantic_model = build_widget_model(widget_def)
20 def widget_tool(**kwargs: Any) -> WidgetComponentBase:
21 return render_widget_definition(widget_def, **kwargs)
23 camel_name = _to_camel_case(_sanitize_tool_name(widget_def.name))
24 widget_tool.__name__ = camel_name
25 widget_tool.__doc__ = (
26 f"Generate a {widget_def.name} widget.\n\n"
27 f"This tool creates a {widget_def.name} widget with the provided data.\n"
28 f"The input must conform to the widget's JSON schema."
29 )
31 parameters: list[inspect.Parameter] = []
32 for field_name, field_info in pydantic_model.model_fields.items():
33 if field_info.is_required():
34 default = inspect.Parameter.empty
35 else:
36 default = field_info.default
38 parameters.append(
39 inspect.Parameter(
40 field_name,
41 inspect.Parameter.KEYWORD_ONLY,
42 default=default,
43 annotation=field_info.annotation,
44 )
45 )
47 cast(
48 Any,
49 widget_tool,
50 ).__signature__ = inspect.Signature(
51 parameters, return_annotation=WidgetComponentBase
52 )
54 annotations: dict[str, Any] = {
55 field_name: field_info.annotation
56 for field_name, field_info in pydantic_model.model_fields.items()
57 }
58 annotations["return"] = WidgetComponentBase
59 cast(Any, widget_tool).__annotations__ = annotations
61 return widget_tool
64def generate_widget_tools(server: FastMCP, widget_defs: list[WidgetDefinition]) -> None:
65 """Register sanitized FastMCP tools for each widget definition."""
66 for widget_def in widget_defs:
67 tool_func = _create_widget_tool_function(widget_def)
68 tool_name = _sanitize_tool_name(widget_def.name)
69 server.tool(name=tool_name)(tool_func)
72__all__ = [
73 "_sanitize_tool_name",
74 "_to_camel_case",
75 "_create_widget_tool_function",
76 "generate_widget_tools",
77]