Skip to main content
Glama
index.ts14.3 kB
import fetch from "node-fetch"; import { parseStringPromise } from 'xml2js'; // Types matching SDK 1.0.1 interface Tool { name: string; description: string; inputSchema: any; } // Define the tools directly const SECTIONAL_GEONAMES = [ "Albuquerque","Anchorage","Atlanta","Bethel","Billings","Brownsville","Cape Lisburne","Charlotte","Cheyenne","Chicago","Cincinnati","Cold Bay","Dallas-Ft Worth","Dawson","Denver","Detroit","Dutch Harbor","El Paso","Fairbanks","Great Falls","Green Bay","Halifax","Hawaiian Islands","Houston","Jacksonville","Juneau","Kansas City","Ketchikan","Klamath Falls","Kodiak","Lake Huron","Las Vegas","Los Angeles","McGrath","Memphis","Miami","Montreal","New Orleans","New York","Nome","Omaha","Phoenix","Point Barrow","Salt Lake City","San Antonio","San Francisco","Seattle","Seward","St Louis","Twin Cities","Washington","Western Aleutian Islands","Whitehorse","Wichita" ]; const TAC_GEONAMES = [ "Anchorage-Fairbanks","Atlanta","Baltimore-Washington","Boston","Charlotte","Chicago","Cincinnati","Cleveland","Dallas-Ft Worth","Denver-Colorado Springs","Detroit","Houston","Kansas City","Las Vegas","Los Angeles","Memphis","Miami","Minneapolis-St Paul","New Orleans","New York","Philadelphia","Phoenix","Pittsburgh","Puerto Rico-VI","St Louis","Salt Lake City","San Diego","San Francisco","Seattle","Tampa-Orlando" ]; const ENROUTE_GEONAMES = ["US","Alaska","Pacific","Caribbean"]; const ENROUTE_SERIES = ["low","high","area"]; const STATE_TO_TPP_REGION = { WA: ["NW1"], OR: ["NW1"], ID: ["NW1"], MT: ["NW1"], WY: ["NW1"], CA: ["SW2", "SW3"], NV: ["SW4"], UT: ["SW4"], AZ: ["SW4"], CO: ["SW1"], NM: ["SW1"], TX: ["SC3", "SC2", "SC5"], OK: ["SC1"], AR: ["SC1"], LA: ["SC4"], MS: ["SC4"], ND: ["NC1"], SD: ["NC1"], MN: ["NC1"], NE: ["NC2"], KS: ["NC2"], IA: ["NC3"], MO: ["NC3"], WI: ["EC3"], IL: ["EC3"], MI: ["EC1"], IN: ["EC2"], OH: ["EC2"], NY: ["NE2"], NJ: ["NE2"], VA: ["NE3"], DC: ["NE3"], CT: ["NE1"], RI: ["NE1"], MA: ["NE1"], VT: ["NE1"], NH: ["NE1"], ME: ["NE1"], PA: ["NE4"], WV: ["NE4"], KY: ["SE1"], TN: ["SE1"], NC: ["SE2"], SC: ["SE2"], GA: ["SE4"], AL: ["SE4"], FL: ["SE3"], PR: ["SE3"], VI: ["SE3"], AK: ["AK"], }; const SUPPLEMENT_VOLUMES = [ "NORTHWEST","SOUTHWEST","NORTH CENTRAL","SOUTH CENTRAL","EAST CENTRAL","SOUTHEAST","NORTHEAST","PACIFIC","ALASKA" ]; // Export tools for use by the main server export const TOOLS: Tool[] = [ { name: "get_sectional", description: "Retrieves sectional charts", inputSchema: { type: "object", properties: { geoname: { type: "string", enum: SECTIONAL_GEONAMES, description: "City or region name for the chart (e.g., 'New York', 'Chicago')" }, edition: { type: "string", enum: ["current", "next"], default: "current", description: "Edition of the chart" }, format: { type: "string", enum: ["pdf", "tiff"], default: "pdf", description: "Format of the chart" } }, required: ["geoname"] } }, { name: "get_tac", description: "Retrieves Terminal Area Charts (TAC)", inputSchema: { type: "object", properties: { geoname: { type: "string", enum: TAC_GEONAMES, description: "City or region name for the chart (e.g., 'New York', 'Chicago')" }, edition: { type: "string", enum: ["current", "next"], default: "current", description: "Edition of the chart" }, format: { type: "string", enum: ["pdf", "tiff"], default: "pdf", description: "Format of the chart" } }, required: ["geoname"] } }, { name: "get_enroute", description: "Retrieves IFR Enroute Charts", inputSchema: { type: "object", properties: { geoname: { type: "string", enum: ENROUTE_GEONAMES, description: "Geographic region for requested chart" }, seriesType: { type: "string", enum: ENROUTE_SERIES, description: "Type of enroute chart (low altitude, high altitude, or area)" }, edition: { type: "string", enum: ["current", "next"], default: "current", description: "Edition of the chart" }, format: { type: "string", enum: ["pdf", "tiff"], default: "pdf", description: "Format of the chart" } }, required: ["geoname", "seriesType"] } }, { name: "get_tpp", description: "Retrieves Terminal Procedures Publication (TPP) charts", inputSchema: { type: "object", properties: { geoname: { type: "string", description: "Geographic region (default US, or two-letter state code)" }, edition: { type: "string", enum: ["current", "next", "changeset"], default: "current", description: "Edition of the chart" } }, required: [] } }, { name: "get_supplement", description: "Retrieves Supplement chart download information", inputSchema: { type: "object", properties: { edition: { type: "string", enum: ["current", "next"], default: "current", description: "Edition of the chart" }, volume: { type: "string", enum: SUPPLEMENT_VOLUMES, description: "Requested volume of Supplement chart set. If omitted, the complete US set is returned." } } } }, ]; // Base URL for FAA chart APIs const BASE_URL = 'https://external-api.faa.gov/apra'; // Tool handlers function checkEnum(val, allowed, param) { if (val && !allowed.includes(val)) { throw new Error(`Invalid value for ${param}: ${val}`); } } async function handleSectional(geoname, edition = 'current', format = 'pdf') { checkEnum(geoname, SECTIONAL_GEONAMES, 'geoname'); checkEnum(format, ["pdf", "tiff"], 'format'); checkEnum(edition, ["current", "next"], 'edition'); const url = new URL(`${BASE_URL}/vfr/sectional/chart`); url.searchParams.append("geoname", geoname); url.searchParams.append("format", format); url.searchParams.append("edition", edition); const response = await fetch(url.toString()); if (!response.ok) { return { content: [{ type: "text", text: `Error: ${response.status} ${response.statusText}` }], isError: true }; } const data = await response.text(); return { content: [{ type: "text", text: data }], isError: false }; } async function handleTAC(geoname, edition = 'current', format = 'pdf') { checkEnum(geoname, TAC_GEONAMES, 'geoname'); checkEnum(format, ["pdf", "tiff"], 'format'); checkEnum(edition, ["current", "next"], 'edition'); const url = new URL(`${BASE_URL}/vfr/tac/chart`); url.searchParams.append("geoname", geoname); url.searchParams.append("format", format); url.searchParams.append("edition", edition); const response = await fetch(url.toString()); if (!response.ok) { return { content: [{ type: "text", text: `Error: ${response.status} ${response.statusText}` }], isError: true }; } const data = await response.text(); return { content: [{ type: "text", text: data }], isError: false }; } async function handleEnroute(geoname, seriesType, edition = 'current', format = 'pdf') { checkEnum(geoname, ENROUTE_GEONAMES, 'geoname'); checkEnum(seriesType, ENROUTE_SERIES, 'seriesType'); checkEnum(format, ["pdf", "tiff"], 'format'); checkEnum(edition, ["current", "next"], 'edition'); const url = new URL(`${BASE_URL}/enroute/chart`); url.searchParams.append("geoname", geoname); url.searchParams.append("seriesType", seriesType); url.searchParams.append("format", format); url.searchParams.append("edition", edition); const response = await fetch(url.toString()); if (!response.ok) { return { content: [{ type: "text", text: `Error: ${response.status} ${response.statusText}` }], isError: true }; } const data = await response.text(); return { content: [{ type: "text", text: data }], isError: false }; } async function handleTPP(geoname = 'US', edition = 'current') { checkEnum(edition, ["current", "next", "changeset"], 'edition'); // Always use US for the API call const url = new URL(`${BASE_URL}/dtpp/chart`); url.searchParams.append("geoname", "US"); url.searchParams.append("edition", edition); const response = await fetch(url.toString()); if (!response.ok) { return { content: [{ type: "text", text: `Error: ${response.status} ${response.statusText}` }], isError: true }; } const xml = await response.text(); const parsed = await parseStringPromise(xml, { explicitArray: false }); // Defensive: handle both single and multiple edition nodes const editions = Array.isArray(parsed.productSet.edition) ? parsed.productSet.edition : [parsed.productSet.edition]; // Find the upload identifier and edition date from the first edition const firstEdition = editions[0]; const productUrl = firstEdition.product.$.url || firstEdition.product.url; const match = productUrl.match(/\/([^\/]+)\/terminal\//); const uploadId = match ? match[1] : null; const editionDate = firstEdition.editionDate; // List of all possible regions const allRegions = [ "AK","EC1","EC2","EC3","NC1","NC2","NC3","NE1","NE2","NE3","NE4","NW1", "SC1","SC2","SC3","SC4","SC5","SE1","SE2","SE3","SE4","SW1","SW2","SW3","SW4" ]; // Determine which regions to return let regions = allRegions; if (geoname && geoname !== 'US') { const code = geoname.trim().toUpperCase(); if (STATE_TO_TPP_REGION[code]) { regions = STATE_TO_TPP_REGION[code]; } else { return { content: [{ type: "text", text: `Unknown state or region: ${geoname}` }], isError: true }; } } // Convert editionDate from MM/DD/YYYY to YYYY-MM-DD let editionDateUrl = editionDate; if (editionDate && editionDate.match(/^\d{2}\/\d{2}\/\d{4}$/)) { const [mm, dd, yyyy] = editionDate.split('/'); editionDateUrl = `${yyyy}-${mm}-${dd}`; } return { content: [{ type: "text", text: JSON.stringify({ editionDate: editionDateUrl, regions: regions.map(region => ({ region, url: `https://aeronav.faa.gov/${uploadId}/terminal/${editionDateUrl}/${region}.pdf` })) }) }], isError: false }; } async function handleSupplement(edition = 'current', volume) { checkEnum(edition, ["current", "next"], 'edition'); if (volume) checkEnum(volume, SUPPLEMENT_VOLUMES, 'volume'); const url = new URL(`${BASE_URL}/supplement/chart`); url.searchParams.append("edition", edition); const response = await fetch(url.toString()); if (!response.ok) { return { content: [{ type: "text", text: `Error: ${response.status} ${response.statusText}` }], isError: true }; } const xml = await response.text(); let parsed; try { parsed = await parseStringPromise(xml, { explicitArray: false }); } catch (e) { return { content: [{ type: "text", text: `Error parsing FAA XML: ${e}` }], isError: true }; } let editionNode = parsed.productSet.edition; if (Array.isArray(editionNode)) editionNode = editionNode[0]; let productNode = editionNode.product; if (Array.isArray(productNode)) productNode = productNode[0]; const urlField = productNode.$?.url || productNode.url; // Extract upload ID and edition date const uploadMatch = urlField.match(/\/([Uu]pload_[^/]+)\/supplements/); const uploadId = uploadMatch ? uploadMatch[1] : null; const editionDateRaw = editionNode.editionDate; // Convert MM/DD/YYYY to YYYYMMDD let editionDate = editionDateRaw; if (editionDateRaw && editionDateRaw.match(/^\d{2}\/\d{2}\/\d{4}$/)) { const [mm, dd, yyyy] = editionDateRaw.split('/'); editionDate = `${yyyy}${mm}${dd}`; } if (!volume || volume === 'US') { return { content: [ { type: "text", text: JSON.stringify({ editionDate, region: "US", url: `https://aeronav.faa.gov/${uploadId}/supplements/DCS_${editionDate}.zip`, }) } ], isError: false, }; } const VOLUME_TO_CODE = { "NORTHWEST": "NW", "SOUTHWEST": "SW", "NORTH CENTRAL": "NC", "SOUTH CENTRAL": "SC", "EAST CENTRAL": "EC", "SOUTHEAST": "SE", "NORTHEAST": "NE", "PACIFIC": "PAC", "ALASKA": "AK" }; const regionCode = VOLUME_TO_CODE[volume]; return { content: [ { type: "text", text: JSON.stringify({ editionDate, region: regionCode, url: `https://aeronav.faa.gov/${uploadId}/supplements/CS_${regionCode}_${editionDate}.pdf`, }) } ], isError: false, }; } // Export unified handler function for the main server export async function handleToolCall(toolName: string, args: any) { try { switch (toolName) { case "get_sectional": { const { geoname, edition, format } = args; return await handleSectional(geoname, edition, format); } case "get_tac": { const { geoname, edition, format } = args; return await handleTAC(geoname, edition, format); } case "get_enroute": { const { geoname, seriesType, edition, format } = args; return await handleEnroute(geoname, seriesType, edition, format); } case "get_tpp": { const { geoname, edition } = args; return await handleTPP(geoname, edition); } case "get_supplement": { const { edition, volume } = args; return await handleSupplement(edition, volume); } default: return { content: [{ type: "text", text: `Unknown tool: ${toolName}` }], isError: true }; } } catch (error) { return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } }

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/blevinstein/aviation-mcp'

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