import gradio as gr import requests import json from typing import Dict, Any, Optional def get_xkcd_comic(comic_id: str = "") -> str: """ Fetch XKCD comic information by ID or get the latest comic. Args: comic_id (str): Comic ID number (leave empty for latest comic) Returns: str: JSON string containing comic information including title, alt text, and image URL """ try: if comic_id.strip(): # Get specific comic by ID url = f"https://xkcd.com/{comic_id.strip()}/info.0.json" else: # Get latest comic url = "https://xkcd.com/info.0.json" response = requests.get(url, timeout=10) response.raise_for_status() comic_data = response.json() # Format the response nicely formatted_response = { "num": comic_data["num"], "title": comic_data["title"], "alt": comic_data["alt"], "img": comic_data["img"], "year": comic_data["year"], "month": comic_data["month"], "day": comic_data["day"], "transcript": comic_data.get("transcript", ""), "safe_title": comic_data["safe_title"] } return json.dumps(formatted_response, indent=2) except requests.exceptions.RequestException as e: return f"Error fetching comic: {str(e)}" except KeyError as e: return f"Error parsing comic data: Missing field {str(e)}" except Exception as e: return f"Unexpected error: {str(e)}" def search_xkcd_transcript(search_term: str) -> str: """ Search for XKCD comics by searching their transcripts and titles. Note: This is a simple demonstration - in a real implementation you'd want a proper search index. Args: search_term (str): Term to search for in comic transcripts and titles Returns: str: JSON string containing matching comics information """ try: # Get latest comic number first latest_response = requests.get("https://xkcd.com/info.0.json", timeout=10) latest_response.raise_for_status() latest_num = latest_response.json()["num"] matches = [] search_term_lower = search_term.lower() # Search through recent comics (last 50 for demo purposes) start_num = max(1, latest_num - 49) for comic_num in range(start_num, latest_num + 1): try: url = f"https://xkcd.com/{comic_num}/info.0.json" response = requests.get(url, timeout=5) response.raise_for_status() comic_data = response.json() # Check if search term is in title, alt text, or transcript if (search_term_lower in comic_data["title"].lower() or search_term_lower in comic_data["alt"].lower() or search_term_lower in comic_data.get("transcript", "").lower()): matches.append({ "num": comic_data["num"], "title": comic_data["title"], "alt": comic_data["alt"][:100] + "..." if len(comic_data["alt"]) > 100 else comic_data["alt"], "img": comic_data["img"] }) except: continue # Skip comics that can't be fetched return json.dumps({"search_term": search_term, "matches": matches}, indent=2) except Exception as e: return f"Search error: {str(e)}" # Create Gradio interface with gr.Blocks(title="XKCD MCP Server") as demo: gr.Markdown("# XKCD MCP Server") gr.Markdown("This server provides tools to fetch and search XKCD comics via MCP protocol.") with gr.Tab("Get Comic"): comic_input = gr.Textbox( label="Comic ID", placeholder="Leave empty for latest comic", value="" ) comic_output = gr.Textbox( label="Comic Data (JSON)", lines=15 ) comic_btn = gr.Button("Get Comic") comic_btn.click(get_xkcd_comic, inputs=[comic_input], outputs=[comic_output]) with gr.Tab("Search Comics"): search_input = gr.Textbox( label="Search Term", placeholder="Enter term to search in titles, alt text, and transcripts" ) search_output = gr.Textbox( label="Search Results (JSON)", lines=15 ) search_btn = gr.Button("Search") search_btn.click(search_xkcd_transcript, inputs=[search_input], outputs=[search_output]) if __name__ == "__main__": demo.launch(mcp_server=True, share=True)