xkcd-mcp-server / app.py
gnumanth's picture
fix: search range
ebcb262 verified
raw
history blame
5.25 kB
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 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)}"
# 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)