Spaces:
Running
Running
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) |