File size: 8,379 Bytes
a4b9277
 
 
69ba8d5
a4b9277
 
69ba8d5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a4b9277
69ba8d5
a4b9277
 
69ba8d5
a4b9277
 
69ba8d5
a4b9277
 
69ba8d5
 
a4b9277
69ba8d5
a4b9277
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69ba8d5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a4b9277
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ebcb262
a38d810
ebcb262
a4b9277
a38d810
a4b9277
 
ebcb262
a4b9277
 
 
37828a6
a4b9277
 
37828a6
a4b9277
 
 
 
 
 
 
 
 
a38d810
ebcb262
a38d810
 
a4b9277
 
 
 
 
 
 
 
69ba8d5
 
 
 
 
 
 
 
a4b9277
 
 
 
 
 
69ba8d5
 
 
 
 
 
 
 
 
 
 
 
a4b9277
 
 
 
69ba8d5
 
 
 
a4b9277
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
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)