File size: 7,926 Bytes
2804df5
49cb9f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2804df5
 
49cb9f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2804df5
49cb9f5
 
 
 
 
 
2804df5
 
49cb9f5
2804df5
49cb9f5
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import gradio as gr
import typing as ty

# Importing the tool files automatically registers them with the mcp_tool registry
# This assumes all tool files are in the 'tools' directory and end with '_tool.py'
# A more robust approach might explicitly import each tool file.
# For this example, we explicitly import the one tool file.
import tools.time_tool

# Import the mcp_tool registry instance
from utils.mcp_decorator import mcp_tool

def update_tool_info(selected_api_name: str, *tool_group_visibility_states: gr.State) -> ty.List[gr.update]:
    """
    Updates the displayed docstring and the visibility of tool UI groups
    based on the selected tool from the dropdown.

    Args:
        selected_api_name: The api_name of the tool selected in the dropdown.
        tool_group_visibility_states: The current state of visibility for each tool group component
                                      (passed automatically by Gradio when they are outputs).

    Returns:
        A list of gr.update objects for the docstring and each tool UI group.
    """
    updates = []

    # 1. Update Docstring
    docstring_update = gr.Markdown.update(visible=False, value="") # Hide and clear by default
    if selected_api_name:
        tool_info = mcp_tool.get_tool_info(selected_api_name)
        if tool_info and tool_info.get('tool_func') and tool_info['tool_func'].__doc__:
             docstring_update = gr.Markdown.update(visible=True, value=f"### Tool Documentation\n---\n{tool_info['tool_func'].__doc__}\n---")

    updates.append(docstring_update)

    # 2. Update Visibility of Tool UI Groups
    # We need to return an update for *each* tool UI group defined in the layout.
    # The order must match the order they appear in the 'outputs' list of the .change() event.
    all_tools_api_names = [api_name for _, api_name in mcp_tool.get_tools_list()]
    
    # Ensure the order of updates matches the order of tool_uis_list created below
    # We'll assume the order from get_tools_list() is consistent.
    
    for api_name_in_list in all_tools_api_names:
         is_selected_tool = (api_name_in_list == selected_api_name)
         # Return gr.update(visible=...) for each tool group
         updates.append(gr.Group.update(visible=is_selected_tool))

    # The total number of updates returned must match the total number of outputs
    # defined in the dropdown.change() call.
    # Outputs are [doc_display, *list_of_tool_groups]
    # Inputs are [dropdown, *list_of_tool_groups]
    # This function signature with *tool_group_visibility_states receiving the
    # current state of the output components is how Gradio handles inputs/outputs
    # when the outputs themselves are also inputs for their own updates (like visibility).
    # However, for simple visibility toggling based *only* on the dropdown value,
    # we don't strictly *need* tool_group_visibility_states as input. We can just
    # return the updates based on the selected_api_name.
    # Let's simplify the function signature and inputs/outputs.
    # The function just needs selected_api_name. It will return the docstring update
    # and visibility updates for *all* groups.

    # Revised update_tool_info:
    # Input: selected_api_name (str)
    # Outputs: doc_display (Markdown), Tool_UI_Group_1 (Group), Tool_UI_Group_2 (Group), ...

    return updates # This list contains updates for docstring + all tool groups

# --- Gradio App Layout ---
with gr.Blocks(title="MCP Server Demo") as demo:
    gr.Markdown("# Gradio MCP Server Demo")
    gr.Markdown("Select a tool to view its documentation and UI controls.")

    # Get defined tools
    tool_options = mcp_tool.get_tools_list() # Returns list of (name, api_name)

    if not tool_options:
        gr.Warning("No tools defined. Please check the 'tools' directory.")
        gr.Markdown("No tools available.")
    else:
        # Dropdown to select tool. Using api_name as value for easier lookup.
        dropdown = gr.Dropdown(
            choices=[(name, api_name) for name, api_name in tool_options],
            label="Select a Tool",
            interactive=True,
        )

        # Markdown component to display tool documentation
        doc_display = gr.Markdown(label="Tool Documentation", visible=False)

        # Container to hold dynamic UI controls.
        # We will create a separate gr.Group for each tool's UI inside this container.
        tool_uis_container = gr.Column()

        # List to hold the UI groups for each tool and map outputs
        tool_ui_groups_list = []

        # Dynamically create UI components for each tool
        with tool_uis_container:
             for tool_name, api_name in tool_options:
                 ui_builder = mcp_tool.get_tool_ui_builder(api_name)
                 if ui_builder:
                     # Call the UI builder function to get the components (should be a gr.Group/Column)
                     tool_ui_group = ui_builder()
                     # Ensure the group is initially hidden
                     tool_ui_group.visible = False
                     # Add the group to our list for output mapping
                     tool_ui_groups_list.append(tool_ui_group)
                 else:
                     # Handle tools defined without a UI builder
                     gr.Markdown(f"Tool '{tool_name}' ({api_name}) has no UI controls defined.", visible=False, elem_id=f"no_ui_{api_name}")
                     # Add a placeholder to maintain output count consistency if necessary,
                     # or ensure build_ui_control always returns a gr.Group (even empty).
                     # Let's assume build_ui_control always returns a group/column.
                     if api_name not in [g.elem_id for g in tool_ui_groups_list if hasattr(g, 'elem_id')]: # Avoid duplicates if builder called earlier
                         # Create a dummy group if builder was missing or returned None unexpectedly
                         with gr.Group(visible=False) as dummy_group:
                             gr.Markdown(f"No UI for {tool_name}")
                         tool_ui_groups_list.append(dummy_group)


        # --- Event Handling ---
        # When the dropdown selection changes, update the displayed info and controls
        dropdown.change(
            fn=update_tool_info,
            inputs=[dropdown], # Just the selected value from the dropdown
            outputs=[doc_display, *tool_ui_groups_list], # Docstring + all tool UI groups for visibility updates
            # Set queue=False for smoother UI updates if needed, depends on complexity
            # queue=False # might cause issues with visibility updates in complex layouts, test if needed
        )
        
        # Trigger an initial update if a default value is set or after loading
        # This ensures the UI is correct when the app first loads if a tool is pre-selected
        # or if you want to show the first tool's info by default.
        # Let's not trigger initially unless a tool is pre-selected in the dropdown.
        # If allow_clear=True and value is None, initial update won't show anything which is fine.


# --- Launch the Gradio App as an MCP Server ---
if __name__ == "__main__":
    # To host on Hugging Face Spaces, use server_name="0.0.0.0" and server_port=7860 (default)
    # The mcp_server=True flag enables the SSE endpoint for MCP clients.
    # Gradio automatically discovers functions decorated with __mcp_tool__ attribute
    # (which our @mcp_tool.define decorator adds) and exposes them via the MCP endpoint.
    print("Launching Gradio app with MCP server enabled...")
    demo.launch(
        server_name="0.0.0.0", # Required for Spaces
        # server_port=7860, # Default port for Spaces, can be omitted
        mcp_server=True,      # Enable the MCP server endpoint
        # share=True,        # Default and recommended for Spaces
    )
    print("Gradio app launched.")