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.") |