mcp_ / app.py
eienmojiki's picture
++
49cb9f5
raw
history blame
7.93 kB
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.")