Skip to main content
Glama
mtaService.ts4.85 kB
import fetch from "node-fetch"; import protobuf from "protobufjs"; let gtfsRootPromise: Promise<protobuf.Root>; const MTA_FEEDS = { 'nqrw': 'https://api-endpoint.mta.info/Dataservice/mtagtfsfeeds/nyct%2Fgtfs-nqrw', '1234567s': 'https://api-endpoint.mta.info/Dataservice/mtagtfsfeeds/nyct%2Fgtfs', 'g': 'https://api-endpoint.mta.info/Dataservice/mtagtfsfeeds/nyct%2Fgtfs-g', 'ace': 'https://api-endpoint.mta.info/Dataservice/mtagtfsfeeds/nyct%2Fgtfs-ace', 'bdfm': 'https://api-endpoint.mta.info/Dataservice/mtagtfsfeeds/nyct%2Fgtfs-bdfm', 'jz': 'https://api-endpoint.mta.info/Dataservice/mtagtfsfeeds/nyct%2Fgtfs-jz', 'l': 'https://api-endpoint.mta.info/Dataservice/mtagtfsfeeds/nyct%2Fgtfs-l', 'si': 'https://api-endpoint.mta.info/Dataservice/mtagtfsfeeds/nyct%2Fgtfs-si' }; const initializeGTFS = (): Promise<protobuf.Root> => { if (!gtfsRootPromise) { gtfsRootPromise = protobuf.load(["src/schemas/gtfs-realtime.proto", "src/schemas/nyct-subway.proto"]); } return gtfsRootPromise; }; // Initialize the promise on module load gtfsRootPromise = initializeGTFS(); async function fetchSingleFeed(url: string) { const root = await gtfsRootPromise; const FeedMessage = root.lookupType("transit_realtime.FeedMessage"); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); try { const res = await fetch(url, { signal: controller.signal, headers: { 'User-Agent': 'whereismytrain-mcp/1.0' } }); clearTimeout(timeoutId); if (!res.ok) { throw new Error(`HTTP ${res.status}: ${res.statusText}`); } const buffer = Buffer.from(await res.arrayBuffer()); if (buffer.length === 0) { throw new Error('Empty response from feed'); } const message = FeedMessage.decode(buffer); const decoded = FeedMessage.toObject(message, { longs: String, enums: String, bytes: String }); if (!decoded || !Array.isArray(decoded.entity)) { throw new Error('Invalid feed structure'); } return decoded; } catch (error: any) { clearTimeout(timeoutId); if (error.name === 'AbortError') { throw new Error('Feed request timed out after 10 seconds'); } throw error; } } export async function fetchMTAData() { const feedKeys = Object.keys(MTA_FEEDS); const feedUrls = Object.values(MTA_FEEDS); const allFeeds = await Promise.allSettled( feedUrls.map(url => fetchSingleFeed(url)) ); const combinedData = { entity: [] as any[], header: null as any, feedStatus: { successful: 0, failed: 0, failedFeeds: [] as string[] } }; allFeeds.forEach((result, index) => { const feedName = feedKeys[index]; if (result.status === 'fulfilled') { const feedData = result.value; if (feedData && feedData.entity) { combinedData.entity.push(...feedData.entity); combinedData.feedStatus.successful++; } if (!combinedData.header && feedData.header) { combinedData.header = feedData.header; } } else { combinedData.feedStatus.failed++; combinedData.feedStatus.failedFeeds.push(feedName); console.error(`Failed to fetch feed ${feedName}`); } }); if (combinedData.feedStatus.failed > 0) { console.warn(`⚠️ ${combinedData.feedStatus.failed}/${feedKeys.length} feeds failed: ${combinedData.feedStatus.failedFeeds.join(', ')}`); } return combinedData; } export async function fetchMTAAlerts() { const root = await gtfsRootPromise; const FeedMessage = root.lookupType("transit_realtime.FeedMessage"); const url = "https://api-endpoint.mta.info/Dataservice/mtagtfsfeeds/camsys%2Fsubway-alerts"; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); try { const res = await fetch(url, { signal: controller.signal, headers: { 'User-Agent': 'whereismytrain-mcp/1.0' } }); clearTimeout(timeoutId); if (!res.ok) { throw new Error(`HTTP ${res.status}: ${res.statusText}`); } const buffer = Buffer.from(await res.arrayBuffer()); if (buffer.length === 0) { throw new Error('Empty alerts response'); } const message = FeedMessage.decode(buffer); const decoded = FeedMessage.toObject(message, { longs: String, enums: String, bytes: String }); if (!decoded || !Array.isArray(decoded.entity)) { throw new Error('Invalid alerts feed structure'); } return decoded; } catch (error: any) { clearTimeout(timeoutId); if (error.name === 'AbortError') { throw new Error('Alerts request timed out after 10 seconds'); } throw error; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sasabasara/where_is_my_train_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server