@siva-sub/mcp-public-transport
Version:
A Model Context Protocol server for Singapore transport data with real-time information and routing
93 lines (92 loc) • 3.53 kB
JavaScript
import { z } from 'zod';
import { BaseTool } from '../base.js';
import { validateInput, BusStopCodeSchema } from '../../utils/validation.js';
import { logger } from '../../utils/logger.js';
const BusArrivalInputSchema = z.object({
busStopCode: BusStopCodeSchema,
serviceNo: z.string().optional(),
format: z.enum(['detailed', 'user_friendly']).default('detailed'),
});
export class BusArrivalTool extends BaseTool {
ltaService;
constructor(ltaService) {
super();
this.ltaService = ltaService;
}
getDefinitions() {
return [
{
name: 'get_bus_arrival',
description: 'Get real-time bus arrival times and information for a specific bus stop',
inputSchema: this.createSchema({
busStopCode: {
type: 'string',
description: '5-digit bus stop code (e.g., "83139")',
pattern: '^[0-9]{5}$',
},
serviceNo: {
type: 'string',
description: 'Optional specific bus service number (e.g., "15")',
},
format: {
type: 'string',
enum: ['detailed', 'user_friendly'],
default: 'detailed',
description: 'Response format preference',
},
}, ['busStopCode']),
},
];
}
canHandle(toolName) {
return toolName === 'get_bus_arrival';
}
async execute(toolName, args) {
try {
const { busStopCode, serviceNo, format } = validateInput(BusArrivalInputSchema, args);
logger.info(`Getting bus arrivals for stop ${busStopCode}`, { serviceNo, format });
const services = await this.ltaService.getBusArrival(busStopCode, serviceNo);
if (format === 'user_friendly') {
return this.formatUserFriendly(services, busStopCode);
}
return {
busStopCode,
services,
totalServices: services.length,
timestamp: new Date().toISOString(),
};
}
catch (error) {
logger.error(`Bus arrival tool failed: ${toolName}`, error);
return this.formatError(error, toolName);
}
}
formatUserFriendly(services, busStopCode) {
if (services.length === 0) {
return {
message: `No buses arriving soon at stop ${busStopCode}`,
busStopCode,
timestamp: new Date().toISOString(),
};
}
const summaries = services.map(service => {
const nextBus = service.nextBuses[0];
if (!nextBus)
return null;
const timeMsg = nextBus.minutesAway === 0
? 'Arriving now'
: nextBus.minutesAway === 1
? '1 minute'
: `${nextBus.minutesAway} minutes`;
const wheelchairIcon = nextBus.wheelchairAccessible ? ' ♿' : '';
return `Bus ${service.serviceNo}: ${timeMsg} (${nextBus.loadDescription})${wheelchairIcon}`;
}).filter(Boolean);
return {
busStopCode,
summary: summaries.join(' • '),
nextArrivals: summaries,
services,
timestamp: new Date().toISOString(),
};
}
}