import gradio as gr import inspect import typing from typing import get_type_hints from academia_mcp.tools import ( arxiv_search, arxiv_download, s2_citations, hf_datasets_search, anthology_search, ) def infer_gradio_interface(func): """ Automatically infer Gradio interface parameters from function signature and docstring. """ # Get function signature sig = inspect.signature(func) type_hints = get_type_hints(func) # Parse docstring docstring = inspect.getdoc(func) or "" # Extract title and description title = func.__name__.replace("_", " ").title() # Use the full docstring as description, convert newlines to HTML breaks if docstring.strip(): description = docstring.strip().replace("\n", "
") else: description = f"Interface for {func.__name__}" # Infer inputs inputs = [] for param_name, param in sig.parameters.items(): param_type = type_hints.get(param_name, str) # Get default value if available default_value = None if param.default is not inspect.Parameter.empty: default_value = param.default label = param_name.replace("_", " ").title() if param_type == str or param_type == typing.Optional[str]: inputs.append( gr.Textbox( label=label, placeholder=f"Enter {param_name}", value=default_value if default_value is not None else "", ) ) elif param_type == int or param_type == typing.Optional[int]: inputs.append( gr.Number( label=label, precision=0, value=default_value if default_value is not None else 0, ) ) elif param_type == float or param_type == typing.Optional[float]: inputs.append( gr.Number( label=label, value=default_value if default_value is not None else 0.0, ) ) elif param_type == bool or param_type == typing.Optional[bool]: inputs.append( gr.Checkbox( label=label, value=default_value if default_value is not None else False, ) ) else: # Default to textbox for unknown types inputs.append( gr.Textbox( label=label, value=(str(default_value) if default_value is not None else ""), ) ) # Infer outputs return_type = type_hints.get("return", str) if return_type == str: outputs = gr.Textbox(label="Result") elif return_type == int: outputs = gr.Number(label="Result", precision=0) elif return_type == float: outputs = gr.Number(label="Result") elif return_type == list: outputs = gr.JSON(label="Results") elif return_type == dict: outputs = gr.JSON(label="Result") else: # Default to textbox for unknown return types outputs = gr.Textbox(label="Result") return { "fn": func, "inputs": inputs, "outputs": outputs, "title": title, "description": description, } arxiv_search_interface = infer_gradio_interface(arxiv_search) arxiv_download_interface = infer_gradio_interface(arxiv_download) s2_citations_interface = infer_gradio_interface(s2_citations) hf_datasets_search_interface = infer_gradio_interface(hf_datasets_search) anthology_search_interface = infer_gradio_interface(anthology_search) search_demo = gr.Interface(**arxiv_search_interface) download_demo = gr.Interface(**arxiv_download_interface) s2_citations_demo = gr.Interface(**s2_citations_interface) hf_datasets_search_demo = gr.Interface(**hf_datasets_search_interface) anthology_search_demo = gr.Interface(**anthology_search_interface) demo = gr.TabbedInterface( [ search_demo, download_demo, s2_citations_demo, hf_datasets_search_demo, anthology_search_demo, ], [ "ArXiv Search", "ArXiv Download", "S2 Citations", "HF Datasets Search", "Anthology Search", ], title="Academia MCP Tools", ) if __name__ == "__main__": demo.launch(mcp_server=True)