Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import requests | |
| import json | |
| import random | |
| from typing import Dict, Any, Optional | |
| def get_latest() -> str: | |
| """ | |
| Fetch the latest XKCD comic. | |
| Returns: | |
| str: JSON string containing latest comic information | |
| """ | |
| try: | |
| url = "https://xkcd.com/info.0.json" | |
| response = requests.get(url, timeout=10) | |
| response.raise_for_status() | |
| comic_data = response.json() | |
| 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 latest 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 get_comic(comic_id: str) -> str: | |
| """ | |
| Fetch a specific XKCD comic by ID. | |
| Args: | |
| comic_id (str): Comic ID number | |
| Returns: | |
| str: JSON string containing comic information | |
| """ | |
| try: | |
| if not comic_id.strip(): | |
| return "Error: Comic ID is required" | |
| url = f"https://xkcd.com/{comic_id.strip()}/info.0.json" | |
| response = requests.get(url, timeout=10) | |
| response.raise_for_status() | |
| comic_data = response.json() | |
| 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 {comic_id}: {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 get_random() -> str: | |
| """ | |
| Fetch a random XKCD comic. | |
| Returns: | |
| str: JSON string containing random comic information | |
| """ | |
| try: | |
| # First get the latest comic to know the range | |
| latest_response = requests.get("https://xkcd.com/info.0.json", timeout=10) | |
| latest_response.raise_for_status() | |
| latest_num = latest_response.json()["num"] | |
| # Generate random comic ID (excluding 404 which doesn't exist) | |
| comic_id = random.randint(1, latest_num) | |
| if comic_id == 404: | |
| comic_id = 405 | |
| url = f"https://xkcd.com/{comic_id}/info.0.json" | |
| response = requests.get(url, timeout=10) | |
| response.raise_for_status() | |
| comic_data = response.json() | |
| 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 random 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 comics that are more likely to have transcripts (1-500 range for faster results) | |
| # Recent comics often don't have transcripts, so we search older ones first | |
| max_search_range = min(500, latest_num) | |
| for comic_num in range(1, max_search_range + 1): | |
| try: | |
| url = f"https://xkcd.com/{comic_num}/info.0.json" | |
| response = requests.get(url, timeout=2) | |
| response.raise_for_status() | |
| comic_data = response.json() | |
| # Check if search term is in title, alt text, safe_title, 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("safe_title", "").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"] | |
| }) | |
| # Limit results to prevent long search times | |
| if len(matches) >= 10: | |
| break | |
| 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)}" | |
| # Helper function for the Gradio interface | |
| def get_xkcd_comic(comic_id: str = "") -> str: | |
| """Wrapper function for Gradio interface compatibility""" | |
| if not comic_id.strip(): | |
| return get_latest() | |
| else: | |
| return get_comic(comic_id) | |
| # 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"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| latest_btn = gr.Button("Get Latest Comic") | |
| random_btn = gr.Button("Get Random Comic") | |
| with gr.Column(): | |
| comic_input = gr.Textbox( | |
| label="Comic ID", | |
| placeholder="Enter comic ID number", | |
| value="" | |
| ) | |
| specific_btn = gr.Button("Get Specific Comic") | |
| comic_output = gr.Textbox( | |
| label="Comic Data (JSON)", | |
| lines=15 | |
| ) | |
| latest_btn.click(get_latest, outputs=[comic_output]) | |
| random_btn.click(get_random, outputs=[comic_output]) | |
| specific_btn.click(get_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) |