Subham9126 commited on
Commit
92ef4cf
·
verified ·
1 Parent(s): 72ca416

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +600 -27
app.py CHANGED
@@ -1,27 +1,600 @@
1
- import gradio as gr
2
- from duckduckgo_search import DDGS
3
- import spaces
4
-
5
- MAX_RESULTS = 5
6
-
7
- @spaces.queue # ✅ CPU-safe, enables Streamable HTTP MCP
8
- def search_duckduckgo(query: str) -> str:
9
- with DDGS() as ddgs:
10
- results = ddgs.text(query, max_results=MAX_RESULTS)
11
- return "\n".join([f"{r['title']} - {r['href']}" for r in results])
12
-
13
- with gr.Blocks() as app:
14
- gr.Markdown("# DuckDuckGo Search Tool")
15
- gr.Markdown("## Example Video")
16
- gr.HTML(
17
- """<iframe width="560" height="315"
18
- src="https://www.youtube.com/embed/0EE3sD1MHeg"
19
- frameborder="0" allowfullscreen></iframe>"""
20
- )
21
-
22
- query_input = gr.Textbox(label="Enter Search Query")
23
- search_btn = gr.Button("Search")
24
- output = gr.Textbox(label="Results", interactive=False)
25
- search_btn.click(fn=search_duckduckgo, inputs=query_input, outputs=output)
26
-
27
- app.launch(mcp_server=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ MCP Server for Indian Railway Seat Availability Checker
3
+ ========================================================
4
+
5
+ This MCP server provides tools for checking train availability between Indian railway stations.
6
+ It uses the RailYatri API to fetch real-time seat availability data.
7
+
8
+ Installation:
9
+ pip install fastmcp aiohttp requests
10
+
11
+ Usage:
12
+ python train_mcp_server.py
13
+ """
14
+
15
+ import asyncio
16
+ import aiohttp
17
+ import requests
18
+ import re
19
+ import logging
20
+ from typing import List, Dict, Any, Union, Optional
21
+ from datetime import datetime
22
+ from fastmcp import FastMCP
23
+
24
+ # Configure logging
25
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
26
+ logger = logging.getLogger(__name__)
27
+
28
+ # --- Configuration Constants ---
29
+ API_BASE_URL = "https://www.railyatri.in/get-next-days-sa-data"
30
+ TRAINS_API_URL = "https://trainticketapi.railyatri.in/api/trains-between-station-with-sa.json"
31
+ DEFAULT_QUOTA = "GN"
32
+ REQUEST_TIMEOUT = 30
33
+ MAX_RETRIES = 3
34
+ RETRY_DELAY = 1
35
+
36
+ # Valid quota codes
37
+ VALID_QUOTAS = {
38
+ "GN": "General",
39
+ "TQ": "Tatkal",
40
+ "PT": "Premium Tatkal",
41
+ "LD": "Ladies",
42
+ "HP": "Handicapped",
43
+ "YU": "Yuva",
44
+ "SS": "Senior Citizen",
45
+ "DF": "Defence",
46
+ "RE": "Railway Employee",
47
+ "RQ": "RAC"
48
+ }
49
+
50
+ # Initialize MCP server
51
+ mcp = FastMCP("Indian Railway Seat Checker")
52
+
53
+ # --- Custom Exceptions ---
54
+ class TrainAPIError(Exception):
55
+ """Custom exception for train API related errors."""
56
+ pass
57
+
58
+ class ValidationError(Exception):
59
+ """Custom exception for input validation errors."""
60
+ pass
61
+
62
+ # --- Validation Functions ---
63
+ def validate_station_code(station_code: str, field_name: str) -> str:
64
+ """Validate and normalize station code."""
65
+ if not isinstance(station_code, str):
66
+ raise ValidationError(f"{field_name} must be a string")
67
+
68
+ station = station_code.strip().upper()
69
+ if not station or len(station) < 2:
70
+ raise ValidationError(f"Invalid {field_name}: {station_code}")
71
+
72
+ return station
73
+
74
+ def validate_date_format(date_str: str, input_format: str = None) -> str:
75
+ """Validate date and convert to YYYY-M-D format."""
76
+ if not isinstance(date_str, str):
77
+ raise ValidationError("Date must be a string")
78
+
79
+ date_str = date_str.strip()
80
+
81
+ formats_to_try = []
82
+ if input_format == 'dd-mm-yyyy':
83
+ formats_to_try = ['%d-%m-%Y', '%d-%m-%y']
84
+ elif input_format == 'yyyy-mm-dd':
85
+ formats_to_try = ['%Y-%m-%d', '%Y-%M-%d']
86
+ else:
87
+ formats_to_try = ['%d-%m-%Y', '%Y-%m-%d', '%d-%m-%y', '%Y-%M-%d']
88
+
89
+ for fmt in formats_to_try:
90
+ try:
91
+ dt_obj = datetime.strptime(date_str, fmt)
92
+ return dt_obj.strftime('%Y-%m-%d')
93
+ except ValueError:
94
+ continue
95
+
96
+ raise ValidationError(f"Invalid date format: {date_str}")
97
+
98
+ def validate_quota(quota: str) -> str:
99
+ """Validate quota code."""
100
+ if not isinstance(quota, str):
101
+ raise ValidationError("Quota must be a string")
102
+
103
+ quota = quota.strip().upper()
104
+ if quota not in VALID_QUOTAS:
105
+ valid_codes = ', '.join(VALID_QUOTAS.keys())
106
+ raise ValidationError(f"Invalid quota '{quota}'. Valid codes: {valid_codes}")
107
+
108
+ return quota
109
+
110
+ def validate_query(query: Dict[str, Any]) -> None:
111
+ """Validates a single query dictionary."""
112
+ required_fields = ["train_number", "journey_class", "source", "destination", "journey_date"]
113
+
114
+ for field in required_fields:
115
+ if field not in query or query[field] is None:
116
+ raise ValidationError(f"Missing required field: {field}")
117
+
118
+ train_num = str(query["train_number"]).strip()
119
+ if not train_num or not train_num.isdigit():
120
+ raise ValidationError(f"Invalid train_number: {query['train_number']}")
121
+
122
+ journey_class = query["journey_class"]
123
+ if isinstance(journey_class, str):
124
+ journey_class = [journey_class]
125
+ elif not isinstance(journey_class, list):
126
+ raise ValidationError(f"journey_class must be string or list")
127
+
128
+ for j_class in journey_class:
129
+ if not isinstance(j_class, str) or not j_class.strip():
130
+ raise ValidationError(f"Invalid journey_class: {j_class}")
131
+
132
+ query["source"] = validate_station_code(query["source"], "source")
133
+ query["destination"] = validate_station_code(query["destination"], "destination")
134
+ query["journey_date"] = validate_date_format(query["journey_date"], 'yyyy-mm-dd')
135
+
136
+ if "journey_quota" in query and query["journey_quota"] is not None:
137
+ query["journey_quota"] = validate_quota(query["journey_quota"])
138
+
139
+ # --- Core Functions ---
140
+ def get_trains_between_stations(source: str, destination: str, journey_date: str) -> Optional[Dict[str, Any]]:
141
+ """Fetch train details between two stations."""
142
+ try:
143
+ source = validate_station_code(source, "source")
144
+ destination = validate_station_code(destination, "destination")
145
+
146
+ if not re.match(r'\d{2}-\d{2}-\d{4}', journey_date.strip()):
147
+ try:
148
+ dt = datetime.strptime(journey_date.strip(), '%Y-%m-%d')
149
+ journey_date = dt.strftime('%d-%m-%Y')
150
+ except ValueError:
151
+ raise ValidationError(f"Invalid date format: {journey_date}")
152
+
153
+ params = {
154
+ "from": source,
155
+ "to": destination,
156
+ "dateOfJourney": journey_date.strip()
157
+ }
158
+
159
+ logger.info(f"Fetching trains from {source} to {destination} on {journey_date}")
160
+
161
+ response = requests.get(TRAINS_API_URL, params=params, timeout=REQUEST_TIMEOUT)
162
+ response.raise_for_status()
163
+
164
+ result = response.json()
165
+ logger.info("Successfully fetched train list")
166
+ return result
167
+
168
+ except Exception as e:
169
+ logger.error(f"Error fetching trains: {e}")
170
+ return None
171
+
172
+ def simplify_train_info(api_result: Dict[str, Any], default_quota: str = DEFAULT_QUOTA) -> List[Dict[str, Any]]:
173
+ """Simplify train information from API response."""
174
+ if not isinstance(api_result, dict):
175
+ return []
176
+
177
+ simplified_trains = []
178
+
179
+ def process_train_list(train_list: List[Dict[str, Any]]) -> None:
180
+ if not isinstance(train_list, list):
181
+ return
182
+
183
+ for train in train_list:
184
+ if not isinstance(train, dict):
185
+ continue
186
+
187
+ try:
188
+ raw_date = train.get("train_date", "").strip()
189
+ formatted_date = None
190
+
191
+ if raw_date:
192
+ for date_format in ['%d-%m-%Y', '%Y-%m-%d']:
193
+ try:
194
+ dt_obj = datetime.strptime(raw_date, date_format)
195
+ formatted_date = dt_obj.strftime('%Y-%m-%d')
196
+ break
197
+ except ValueError:
198
+ continue
199
+
200
+ if not formatted_date:
201
+ formatted_date = raw_date
202
+
203
+ journey_classes = train.get("journey_class", [])
204
+ if isinstance(journey_classes, str):
205
+ journey_classes = [journey_classes]
206
+ elif not isinstance(journey_classes, list):
207
+ journey_classes = []
208
+
209
+ journey_classes = [jc.strip() for jc in journey_classes if jc and str(jc).strip()]
210
+
211
+ if not journey_classes:
212
+ continue
213
+
214
+ train_data = {
215
+ "train_number": str(train.get("train_number", "")).strip(),
216
+ "journey_class": journey_classes,
217
+ "source": str(train.get("from", "")).strip().upper(),
218
+ "destination": str(train.get("to", "")).strip().upper(),
219
+ "journey_date": formatted_date,
220
+ "journey_quota": validate_quota(default_quota)
221
+ }
222
+
223
+ if (train_data["train_number"] and
224
+ train_data["source"] and
225
+ train_data["destination"] and
226
+ train_data["journey_date"]):
227
+ simplified_trains.append(train_data)
228
+
229
+ except Exception as e:
230
+ logger.warning(f"Error processing train: {e}")
231
+ continue
232
+
233
+ train_categories = ["train_between_stations", "alternate_trains", "reserved_trains"]
234
+
235
+ for category in train_categories:
236
+ if category in api_result:
237
+ process_train_list(api_result[category])
238
+
239
+ return simplified_trains
240
+
241
+ async def get_train_availability_async(
242
+ session: aiohttp.ClientSession,
243
+ query: Dict[str, Union[str, int]],
244
+ retry_count: int = 0
245
+ ) -> Dict[str, Any]:
246
+ """Asynchronously fetch seat availability data."""
247
+ params = {
248
+ "train_number": str(query["train_number"]).strip(),
249
+ "journey_class": str(query["journey_class"]).strip(),
250
+ "from": str(query["source"]).strip().upper(),
251
+ "to": str(query["destination"]).strip().upper(),
252
+ "journey_date": str(query["journey_date"]).strip(),
253
+ "journey_quota": str(query.get("journey_quota", DEFAULT_QUOTA)).strip(),
254
+ }
255
+
256
+ try:
257
+ timeout = aiohttp.ClientTimeout(total=REQUEST_TIMEOUT)
258
+ async with session.get(API_BASE_URL, params=params, timeout=timeout) as response:
259
+ response.raise_for_status()
260
+ response_data = await response.json()
261
+
262
+ if not isinstance(response_data, dict):
263
+ return {}
264
+
265
+ return response_data
266
+
267
+ except Exception as e:
268
+ logger.error(f"Error for train {params['train_number']}: {e}")
269
+ if retry_count < MAX_RETRIES:
270
+ await asyncio.sleep(RETRY_DELAY * (retry_count + 1))
271
+ return await get_train_availability_async(session, query, retry_count + 1)
272
+ return {}
273
+
274
+ def safe_extract_train_number(ticket_link: str) -> str:
275
+ """Extract train number from ticket link."""
276
+ if not isinstance(ticket_link, str):
277
+ return "N/A"
278
+
279
+ try:
280
+ match = re.search(r'train_no=(\d+)', ticket_link)
281
+ return match.group(1) if match else "N/A"
282
+ except Exception:
283
+ return "N/A"
284
+
285
+ def extract_train_availability(data: Dict[str, Any]) -> List[Dict[str, Any]]:
286
+ """Extract train availability details from API data."""
287
+ if not isinstance(data, dict):
288
+ return []
289
+
290
+ extracted_data = []
291
+
292
+ try:
293
+ seat_availability = None
294
+
295
+ if "result" in data and isinstance(data["result"], dict):
296
+ for key in ["seat_availibility", "seat_availability"]:
297
+ if key in data["result"] and isinstance(data["result"][key], list):
298
+ seat_availability = data["result"][key]
299
+ break
300
+
301
+ if not seat_availability:
302
+ for key in ["seat_availibility", "seat_availability"]:
303
+ if key in data and isinstance(data[key], list):
304
+ seat_availability = data[key]
305
+ break
306
+
307
+ if not seat_availability:
308
+ return []
309
+
310
+ for entry in seat_availability:
311
+ if not isinstance(entry, dict):
312
+ continue
313
+
314
+ ticket_link = entry.get("ticket_link", "")
315
+ train_no = safe_extract_train_number(ticket_link)
316
+
317
+ class_type = None
318
+ for key in ["class_type", "class", "journey_class"]:
319
+ if key in entry and entry[key]:
320
+ class_type = str(entry[key]).strip()
321
+ break
322
+
323
+ if not class_type:
324
+ continue
325
+
326
+ availability = entry.get(class_type)
327
+ if availability is None:
328
+ for key in ["availability", "status", "seats"]:
329
+ if key in entry:
330
+ availability = entry[key]
331
+ break
332
+
333
+ journey_date = entry.get("Date (DD-MM-YYYY)")
334
+ if journey_date is None:
335
+ for key in ["date", "journey_date", "Date"]:
336
+ if key in entry:
337
+ journey_date = entry[key]
338
+ break
339
+
340
+ total_fare = entry.get("total_fare")
341
+ if total_fare is None:
342
+ for key in ["fare", "price", "cost"]:
343
+ if key in entry:
344
+ total_fare = entry[key]
345
+ break
346
+
347
+ extracted_data.append({
348
+ "train_no": train_no,
349
+ "journey_date": journey_date,
350
+ "class_type": class_type,
351
+ "availability": availability,
352
+ "total_fare": total_fare
353
+ })
354
+
355
+ except Exception as e:
356
+ logger.error(f"Error extracting availability: {e}")
357
+ return []
358
+
359
+ return extracted_data
360
+
361
+ async def fetch_all_availabilities(queries: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
362
+ """Fetch train availability for multiple queries."""
363
+ logger.info(f"Processing {len(queries)} queries")
364
+
365
+ tasks = []
366
+ connector = aiohttp.TCPConnector(limit=10, limit_per_host=5)
367
+ timeout = aiohttp.ClientTimeout(total=REQUEST_TIMEOUT)
368
+
369
+ try:
370
+ async with aiohttp.ClientSession(
371
+ connector=connector,
372
+ timeout=timeout,
373
+ headers={'User-Agent': 'Python-TrainAvailability/1.0'}
374
+ ) as session:
375
+
376
+ for query in queries:
377
+ journey_classes = query["journey_class"]
378
+ if isinstance(journey_classes, str):
379
+ journey_classes = [journey_classes.strip()]
380
+ elif isinstance(journey_classes, list):
381
+ journey_classes = [str(jc).strip() for jc in journey_classes if jc]
382
+
383
+ for j_class in journey_classes:
384
+ if not j_class:
385
+ continue
386
+
387
+ single_request_query = query.copy()
388
+ single_request_query["journey_class"] = j_class
389
+
390
+ task = get_train_availability_async(session, single_request_query)
391
+ tasks.append(task)
392
+
393
+ if not tasks:
394
+ return []
395
+
396
+ api_responses = await asyncio.gather(*tasks, return_exceptions=True)
397
+
398
+ except Exception as e:
399
+ logger.error(f"Error executing requests: {e}")
400
+ raise TrainAPIError(f"Failed to execute API requests: {e}")
401
+
402
+ all_availability_data = []
403
+
404
+ for response in api_responses:
405
+ if isinstance(response, Exception) or not isinstance(response, dict) or not response:
406
+ continue
407
+
408
+ try:
409
+ extracted_info = extract_train_availability(response)
410
+ if extracted_info:
411
+ all_availability_data.extend(extracted_info)
412
+ except Exception as e:
413
+ logger.error(f"Error processing response: {e}")
414
+ continue
415
+
416
+ return all_availability_data
417
+
418
+ # --- MCP Tool Definitions ---
419
+
420
+ @mcp.tool()
421
+ async def get_trains(source: str, destination: str, journey_date: str) -> Dict[str, Any]:
422
+ """
423
+ Get list of trains between two stations.
424
+
425
+ Args:
426
+ source: Source station code (e.g., 'HWH' for Howrah, 'NDLS' for New Delhi)
427
+ destination: Destination station code (e.g., 'PNBE' for Patna, 'BCT' for Mumbai Central)
428
+ journey_date: Journey date in 'DD-MM-YYYY' format (e.g., '25-09-2025')
429
+
430
+ Returns:
431
+ Dictionary containing train information with categories:
432
+ - train_between_stations: Direct trains
433
+ - alternate_trains: Alternative train options
434
+ - reserved_trains: Trains with reservation available
435
+
436
+ Example:
437
+ result = await get_trains("HWH", "PNBE", "25-09-2025")
438
+ """
439
+ try:
440
+ result = get_trains_between_stations(source, destination, journey_date)
441
+ if result:
442
+ return {
443
+ "success": True,
444
+ "data": result,
445
+ "message": f"Found trains from {source} to {destination}"
446
+ }
447
+ else:
448
+ return {
449
+ "success": False,
450
+ "data": {},
451
+ "message": "No trains found or API error occurred"
452
+ }
453
+ except Exception as e:
454
+ return {
455
+ "success": False,
456
+ "data": {},
457
+ "message": f"Error: {str(e)}"
458
+ }
459
+
460
+ @mcp.tool()
461
+ async def check_seat_availability(
462
+ source: str,
463
+ destination: str,
464
+ journey_date: str,
465
+ quota: str = "GN"
466
+ ) -> Dict[str, Any]:
467
+ """
468
+ Check seat availability for all trains between two stations.
469
+
470
+ Args:
471
+ source: Source station code (e.g., 'HWH' for Howrah)
472
+ destination: Destination station code (e.g., 'PNBE' for Patna)
473
+ journey_date: Journey date in 'DD-MM-YYYY' format (e.g., '25-09-2025')
474
+ quota: Booking quota (default: 'GN' for General)
475
+ Valid values: GN, TQ (Tatkal), PT (Premium Tatkal), LD (Ladies),
476
+ HP (Handicapped), YU (Yuva), SS (Senior Citizen),
477
+ DF (Defence), RE (Railway Employee), RQ (RAC)
478
+
479
+ Returns:
480
+ Dictionary with availability information including:
481
+ - train_no: Train number
482
+ - journey_date: Date of journey
483
+ - class_type: Class of travel (1A, 2A, 3A, SL, etc.)
484
+ - availability: Seat availability status
485
+ - total_fare: Fare information
486
+
487
+ Example:
488
+ result = await check_seat_availability("HWH", "PNBE", "25-09-2025", "GN")
489
+ """
490
+ try:
491
+ trains_data = get_trains_between_stations(source, destination, journey_date)
492
+ if not trains_data:
493
+ return {
494
+ "success": False,
495
+ "data": [],
496
+ "message": "No trains found between stations"
497
+ }
498
+
499
+ simplified_trains = simplify_train_info(trains_data, quota)
500
+ if not simplified_trains:
501
+ return {
502
+ "success": False,
503
+ "data": [],
504
+ "message": "No valid trains found for checking availability"
505
+ }
506
+
507
+ availability_data = await fetch_all_availabilities(simplified_trains)
508
+
509
+ return {
510
+ "success": True,
511
+ "data": availability_data,
512
+ "trains_checked": len(simplified_trains),
513
+ "availability_records": len(availability_data),
514
+ "message": f"Checked {len(simplified_trains)} trains, found {len(availability_data)} availability records"
515
+ }
516
+
517
+ except Exception as e:
518
+ return {
519
+ "success": False,
520
+ "data": [],
521
+ "message": f"Error: {str(e)}"
522
+ }
523
+
524
+ @mcp.tool()
525
+ async def check_specific_train(
526
+ train_number: str,
527
+ source: str,
528
+ destination: str,
529
+ journey_date: str,
530
+ journey_class: str,
531
+ quota: str = "GN"
532
+ ) -> Dict[str, Any]:
533
+ """
534
+ Check seat availability for a specific train and class.
535
+
536
+ Args:
537
+ train_number: Train number (e.g., '12301')
538
+ source: Source station code (e.g., 'HWH')
539
+ destination: Destination station code (e.g., 'NDLS')
540
+ journey_date: Journey date in 'YYYY-MM-DD' format
541
+ journey_class: Class of travel (e.g., '1A', '2A', '3A', 'SL')
542
+ quota: Booking quota (default: 'GN')
543
+
544
+ Returns:
545
+ Dictionary with availability information for the specific train
546
+
547
+ Example:
548
+ result = await check_specific_train("12301", "HWH", "NDLS", "2025-09-25", "3A", "GN")
549
+ """
550
+ try:
551
+ query = {
552
+ "train_number": train_number,
553
+ "journey_class": journey_class,
554
+ "source": source,
555
+ "destination": destination,
556
+ "journey_date": journey_date,
557
+ "journey_quota": quota
558
+ }
559
+
560
+ validate_query(query)
561
+
562
+ availability_data = await fetch_all_availabilities([query])
563
+
564
+ if availability_data:
565
+ return {
566
+ "success": True,
567
+ "data": availability_data[0] if availability_data else {},
568
+ "message": f"Availability checked for train {train_number}"
569
+ }
570
+ else:
571
+ return {
572
+ "success": False,
573
+ "data": {},
574
+ "message": "No availability data found"
575
+ }
576
+
577
+ except Exception as e:
578
+ return {
579
+ "success": False,
580
+ "data": {},
581
+ "message": f"Error: {str(e)}"
582
+ }
583
+
584
+ @mcp.tool()
585
+ async def get_quota_codes() -> Dict[str, str]:
586
+ """
587
+ Get all valid quota codes and their descriptions.
588
+
589
+ Returns:
590
+ Dictionary of quota codes and their full names
591
+
592
+ Example:
593
+ codes = await get_quota_codes()
594
+ # Returns: {"GN": "General", "TQ": "Tatkal", ...}
595
+ """
596
+ return VALID_QUOTAS
597
+
598
+ # Run the server
599
+ if __name__ == "__main__":
600
+ mcp.run(transport="http")