@siva-sub/mcp-public-transport
Version:
A Model Context Protocol server for Singapore transport data with real-time information and routing
150 lines (149 loc) • 7.19 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SingaporeTransportServer = void 0;
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
const lta_js_1 = require("./services/lta.js");
const onemap_js_1 = require("./services/onemap.js");
const cache_js_1 = require("./services/cache.js");
const logger_js_1 = require("./utils/logger.js");
const errors_js_1 = require("./utils/errors.js");
// Import all tools
const arrival_js_1 = require("./tools/bus/arrival.js");
const stops_js_1 = require("./tools/bus/stops.js");
const search_js_1 = require("./tools/bus/search.js");
const details_js_1 = require("./tools/bus/details.js");
const status_js_1 = require("./tools/train/status.js");
const comprehensive_js_1 = require("./tools/routing/comprehensive.js");
const availability_js_1 = require("./tools/taxi/availability.js");
const search_js_2 = require("./tools/location/search.js");
const landmarks_js_1 = require("./tools/location/landmarks.js");
const conditions_js_1 = require("./tools/weather/conditions.js");
// Import enhanced services
const postalCode_js_1 = require("./services/postalCode.js");
const time_js_1 = require("./services/time.js");
const fuzzySearch_js_1 = require("./services/fuzzySearch.js");
const weather_js_1 = require("./services/weather.js");
const themes_js_1 = require("./services/themes.js");
class SingaporeTransportServer {
constructor(config) {
this.config = config;
this.tools = [];
this.cacheService = new cache_js_1.CacheService(config.cacheDuration);
this.ltaService = new lta_js_1.LTAService(config.ltaAccountKey, this.cacheService, config.requestTimeout);
this.oneMapService = new onemap_js_1.OneMapService(undefined, // No static token needed
config.oneMapEmail, config.oneMapPassword, this.cacheService, config.requestTimeout);
}
async setupTools(server) {
// Initialize enhanced services
const postalCodeService = new postalCode_js_1.PostalCodeService(this.oneMapService, this.cacheService);
const timeService = new time_js_1.SingaporeTimeService();
const fuzzySearchService = new fuzzySearch_js_1.FuzzySearchService();
const weatherService = new weather_js_1.WeatherService(this.cacheService);
const themesService = new themes_js_1.ThemesService(this.oneMapService, this.cacheService);
// Initialize all tools
this.tools = [
new arrival_js_1.BusArrivalTool(this.ltaService),
new stops_js_1.BusStopsTool(this.ltaService, this.oneMapService),
new search_js_1.BusStopSearchTool(this.ltaService, this.oneMapService, fuzzySearchService),
new details_js_1.BusStopDetailsTool(this.ltaService, this.oneMapService),
new status_js_1.TrainStatusTool(this.ltaService),
new comprehensive_js_1.ComprehensiveJourneyTool(this.oneMapService, this.ltaService, weatherService),
new availability_js_1.TaxiAvailabilityTool(this.ltaService, this.oneMapService),
new search_js_2.LocationSearchTool(this.oneMapService, postalCodeService, timeService, fuzzySearchService),
new landmarks_js_1.LandmarksDiscoveryTool(themesService, this.oneMapService),
new conditions_js_1.WeatherConditionsTool(weatherService),
];
// Get all tool definitions
const toolDefinitions = this.tools.flatMap(tool => tool.getDefinitions());
// Set up tool list handler
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
logger_js_1.logger.debug('Listing available tools');
return {
tools: toolDefinitions,
};
});
// Set up tool call handler
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
logger_js_1.logger.info(`Executing tool: ${name}`, {
arguments: args ? Object.keys(args) : [],
});
// Find and execute the appropriate tool
for (const tool of this.tools) {
if (tool.canHandle(name)) {
const startTime = Date.now();
const result = await tool.execute(name, args || {});
const duration = Date.now() - startTime;
logger_js_1.logger.info(`Tool executed successfully: ${name}`, {
duration: `${duration}ms`,
hasError: !!result.error,
});
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
}
throw new errors_js_1.TransportError(`Unknown tool: ${name}`, 'TOOL_NOT_FOUND');
}
catch (error) {
logger_js_1.logger.error(`Tool execution failed: ${name}`, error);
if (error instanceof errors_js_1.TransportError) {
throw error;
}
throw new errors_js_1.TransportError(`Tool execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 'TOOL_EXECUTION_ERROR');
}
});
logger_js_1.logger.info(`Singapore Transport MCP Server initialized with ${toolDefinitions.length} tools`);
}
async healthCheck() {
const checks = {
lta_api: false,
onemap_api: false,
cache: false,
};
try {
// Test LTA API
await this.ltaService.getBusStops(0, 1);
checks.lta_api = true;
}
catch (error) {
logger_js_1.logger.warn('LTA API health check failed', error);
}
try {
// Test OneMap API
await this.oneMapService.geocode('Singapore');
checks.onemap_api = true;
}
catch (error) {
logger_js_1.logger.warn('OneMap API health check failed', error);
}
// Test cache
try {
const testKey = 'health_check_test';
const testValue = { status: 'ok' };
this.cacheService.set(testKey, testValue, 10);
const retrievedValue = this.cacheService.get(testKey);
if (retrievedValue?.status === 'ok') {
checks.cache = true;
}
this.cacheService.del(testKey);
}
catch (error) {
logger_js_1.logger.warn('Cache health check failed', error);
}
const isHealthy = Object.values(checks).every(Boolean);
return {
status: isHealthy ? 'healthy' : 'degraded',
checks,
cache_stats: this.cacheService.getStats(),
timestamp: new Date().toISOString(),
};
}
}
exports.SingaporeTransportServer = SingaporeTransportServer;