geotap-mcp-server 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "geotap-mcp-server",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "MCP server for GeoTap — access 37 US federal environmental and infrastructure data sources from Claude, Cursor, and other AI tools",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -26,7 +26,7 @@ Actions:
26
26
  - "polygon" — Query features in a polygon area. Set geometry="none" and specify layers.
27
27
  - "summary" — Quick feature counts for an area (no geometry, just numbers). Fastest option for "how many?" questions.
28
28
  - "geocode" — Convert address/place name to coordinates. Use only when you need coordinates for other tools.
29
- - "list_layers" — List all 37 available data layers.
29
+ - "list_layers" — List all 71 available data layers.
30
30
  - "layer_details" — Get metadata about a specific layer.
31
31
  - "layer_features" — Get features from one specific layer in a bbox.`,
32
32
  parameters: {
@@ -477,4 +477,126 @@ Actions:
477
477
  specific: 'check_specific_api_status',
478
478
  }
479
479
  },
480
+
481
+ // ═══════════════════════════════════════════════════════════════════
482
+ // 13. GET HAZARDS — Natural hazards, risk, and vulnerability data
483
+ // ═══════════════════════════════════════════════════════════════════
484
+ {
485
+ name: 'get_hazards',
486
+ description: `Query natural hazard and risk data for a location — FEMA risk index, seismic design, wildfires, landslides, coastal vulnerability, flood insurance claims, and social vulnerability.
487
+
488
+ Actions:
489
+ - "nri" — FEMA National Risk Index: overall risk score, expected annual loss, 18 hazard types (earthquake, hurricane, tornado, flooding, wildfire, etc.) at the county level.
490
+ - "seismic_design" — USGS seismic design values (ASCE 7-22): spectral acceleration, peak ground acceleration, site class. Required for structural engineering.
491
+ - "wildfires" — Current and recent wildfire perimeters from NIFC: fire name, acres burned, containment percentage.
492
+ - "landslides" — USGS landslide inventory: historical landslide locations, types, damage reports.
493
+ - "coastal" — USGS Coastal Vulnerability Index: geomorphology, coastal slope, sea level rise, tide range, wave height, erosion rate.
494
+ - "nfip_claims" — FEMA NFIP flood insurance claims by county: dates of loss, amounts paid, flood zones.
495
+ - "social_vulnerability" — CDC Social Vulnerability Index: socioeconomic status, household composition, minority status, housing type (0-1 scale, tract level).`,
496
+ parameters: {
497
+ action: z.enum(['nri', 'seismic_design', 'wildfires', 'landslides', 'coastal', 'nfip_claims', 'social_vulnerability'])
498
+ .describe('Which hazard/risk data to query'),
499
+ lat: z.number().optional().describe('Latitude WGS84 (for seismic_design)'),
500
+ lng: z.number().optional().describe('Longitude WGS84 (for seismic_design)'),
501
+ bbox: z.string().optional().describe('Bounding box "west,south,east,north" (for nri, wildfires, landslides, coastal, social_vulnerability)'),
502
+ countyFips: z.string().optional().describe('5-digit county FIPS code (for nfip_claims)'),
503
+ geometry: z.enum(['none', 'simplified', 'full']).optional().describe('Geometry detail level (default: none)'),
504
+ },
505
+ _actionMap: {
506
+ nri: 'get_nri_risk',
507
+ seismic_design: 'get_seismic_design_values',
508
+ wildfires: 'get_wildfire_perimeters',
509
+ landslides: 'get_landslide_data',
510
+ coastal: 'get_coastal_vulnerability',
511
+ nfip_claims: 'get_nfip_claims',
512
+ social_vulnerability: 'get_social_vulnerability',
513
+ }
514
+ },
515
+
516
+ // ═══════════════════════════════════════════════════════════════════
517
+ // 14. GET ENERGY — Solar, utility rates, EV charging, electricity
518
+ // ═══════════════════════════════════════════════════════════════════
519
+ {
520
+ name: 'get_energy',
521
+ description: `Query energy data for a location — solar resource, solar production estimates, utility rates, EV charging stations.
522
+
523
+ Actions:
524
+ - "solar" — NREL solar resource data: direct normal irradiance (DNI), global horizontal irradiance (GHI), latitude tilt irradiance. Monthly and annual.
525
+ - "pvwatts" — NREL PVWatts solar energy production estimate: annual kWh output, capacity factor. Configurable system size and panel orientation.
526
+ - "utility_rates" — NREL utility rate data: utility company name, residential/commercial/industrial rates ($/kWh).
527
+ - "alt_fuel" — DOE alternative fuel stations: EV chargers, CNG, hydrogen, etc. Includes network, connector types, and availability.`,
528
+ parameters: {
529
+ action: z.enum(['solar', 'pvwatts', 'utility_rates', 'alt_fuel'])
530
+ .describe('Which energy data to query'),
531
+ lat: z.number().optional().describe('Latitude WGS84'),
532
+ lng: z.number().optional().describe('Longitude WGS84'),
533
+ system_capacity: z.number().optional().describe('Solar system size in kW for pvwatts (default: 4)'),
534
+ tilt: z.number().optional().describe('Panel tilt angle for pvwatts (default: 20)'),
535
+ azimuth: z.number().optional().describe('Panel azimuth for pvwatts, 180=south (default: 180)'),
536
+ radius: z.number().optional().describe('Search radius in miles for alt_fuel (default: 25)'),
537
+ },
538
+ _actionMap: {
539
+ solar: 'get_solar_resource',
540
+ pvwatts: 'get_solar_estimate',
541
+ utility_rates: 'get_utility_rates',
542
+ alt_fuel: 'get_alt_fuel_stations',
543
+ }
544
+ },
545
+
546
+ // ═══════════════════════════════════════════════════════════════════
547
+ // 15. GET INFRASTRUCTURE — HIFLD critical infrastructure
548
+ // ═══════════════════════════════════════════════════════════════════
549
+ {
550
+ name: 'get_infrastructure',
551
+ description: `Query critical infrastructure data from HIFLD and DOT — hospitals, fire stations, schools, law enforcement, power plants, EMS, airports, railroad crossings, bridges, and historic places.
552
+
553
+ Actions:
554
+ - "hospitals" — Hospitals: beds, trauma level, helipad, ownership type.
555
+ - "fire_stations" — Fire stations: type, status.
556
+ - "schools" — Public schools: enrollment, grade levels, teacher count.
557
+ - "power_plants" — Power plants: fuel type, installed capacity (MW).
558
+ - "airports" — FAA airports: facility type, ownership, operations counts.
559
+ - "railroad_crossings" — FRA highway-rail crossings: warning devices, trains/day, crash data.
560
+ - "bridges" — DOT bridges: condition ratings (0-9), sufficiency rating (0-100), year built.
561
+ - "historic_places" — National Register of Historic Places: significance, category, period.`,
562
+ parameters: {
563
+ action: z.enum(['hospitals', 'fire_stations', 'schools', 'power_plants', 'airports', 'railroad_crossings', 'bridges', 'historic_places'])
564
+ .describe('Which infrastructure data to query'),
565
+ bbox: z.string().describe('Bounding box "west,south,east,north"'),
566
+ geometry: z.enum(['none', 'simplified', 'full']).optional().describe('Geometry detail level (default: none)'),
567
+ },
568
+ _actionMap: {
569
+ hospitals: 'get_hospitals',
570
+ fire_stations: 'get_fire_stations',
571
+ schools: 'get_schools',
572
+ power_plants: 'get_power_plants',
573
+ airports: 'get_airports',
574
+ railroad_crossings: 'get_railroad_crossings',
575
+ bridges: 'get_bridges',
576
+ historic_places: 'get_historic_places',
577
+ }
578
+ },
579
+
580
+ // ═══════════════════════════════════════════════════════════════════
581
+ // 16. GET ECOLOGY — Species, fish habitat, water quality stations
582
+ // ═══════════════════════════════════════════════════════════════════
583
+ {
584
+ name: 'get_ecology',
585
+ description: `Query ecological and biodiversity data — species occurrences, essential fish habitat, water quality monitoring stations.
586
+
587
+ Actions:
588
+ - "species" — GBIF species occurrence records: scientific name, kingdom, family, observation date, IUCN status.
589
+ - "fish_habitat" — NOAA Essential Fish Habitat: species, habitat type, life stage, fishery management council.`,
590
+ parameters: {
591
+ action: z.enum(['species', 'fish_habitat'])
592
+ .describe('Which ecology data to query'),
593
+ bbox: z.string().describe('Bounding box "west,south,east,north"'),
594
+ limit: z.number().optional().describe('Max records for species (default: 300)'),
595
+ geometry: z.enum(['none', 'simplified', 'full']).optional().describe('Geometry detail level (default: none)'),
596
+ },
597
+ _actionMap: {
598
+ species: 'get_species_occurrences',
599
+ fish_habitat: 'get_fish_habitat',
600
+ }
601
+ },
480
602
  ];
@@ -13,7 +13,7 @@ import { tools } from './tools.js';
13
13
  */
14
14
  const TOOL_CATEGORIES = {
15
15
  'Getting Started': {
16
- keywords: ['start', 'begin', 'address', 'location', 'what is', 'tell me about', 'lookup', 'search address', 'find address'],
16
+ keywords: ['start', 'begin', 'address', 'location', 'what is', 'tell me about', 'lookup', 'search address', 'find address', 'hydric', 'soil type', 'what soil'],
17
17
  tools: ['query_address', 'identify_features_at_point', 'geocode_address']
18
18
  },
19
19
  'Flood & FEMA': {
@@ -25,20 +25,20 @@ const TOOL_CATEGORIES = {
25
25
  tools: ['get_rainfall_data', 'get_idf_curves', 'generate_hyetograph', 'get_rainfall_distribution', 'get_climate_change_rainfall_projection', 'get_rainfall_uncertainty_bounds', 'run_rainfall_sensitivity_analysis']
26
26
  },
27
27
  'Watershed & Hydrology': {
28
- keywords: ['watershed', 'drainage', 'basin', 'delineate', 'hydrology', 'runoff', 'peak flow', 'streamstats', 'catchment', 'time of concentration'],
28
+ keywords: ['watershed', 'drainage', 'basin', 'delineate', 'runoff', 'streamstats', 'catchment', 'time of concentration', 'hydrology analysis', 'hydrologic analysis'],
29
29
  tools: ['delineate_watershed', 'get_watershed_characteristics', 'get_flow_statistics', 'analyze_hydrology', 'get_flowlines']
30
30
  },
31
- 'Curve Number & Soils': {
32
- keywords: ['curve number', 'cn', 'soil', 'hsg', 'hydrologic soil', 'runoff', 'scs', 'nrcs', 'ssurgo', 'infiltration', 'pervious', 'impervious'],
33
- tools: ['analyze_curve_numbers', 'lookup_curve_number', 'get_curve_number_tables']
31
+ 'Curve Number': {
32
+ keywords: ['curve number', 'cn value', 'runoff coefficient', 'scs method', 'nrcs method', 'infiltration rate', 'pervious', 'impervious', 'peak flow', 'peak discharge', 'hydrology'],
33
+ tools: ['analyze_curve_numbers', 'lookup_curve_number', 'get_curve_number_tables', 'analyze_hydrology']
34
34
  },
35
35
  'Water Quality': {
36
- keywords: ['water quality', 'impairment', 'impaired', 'pollution', 'pollutant', 'tmdl', '303d', 'attains', 'epa', 'clean water', 'npdes', 'discharge'],
36
+ keywords: ['water quality', 'impairment', 'impaired', 'pollution', 'pollutant', 'tmdl', '303d', 'attains', 'clean water act'],
37
37
  tools: ['get_water_quality', 'get_water_impairments', 'get_watershed_for_point', 'get_watershed_water_quality']
38
38
  },
39
39
  'Wetlands & Species': {
40
- keywords: ['wetland', 'nwi', 'endangered', 'species', 'habitat', 'critical habitat', 'protected', 'conservation', 'wildlife'],
41
- tools: ['query_address', 'identify_features_at_point', 'get_environmental_data_for_area']
40
+ keywords: ['wetland', 'wetlands near', 'nwi', 'endangered', 'species', 'habitat', 'critical habitat', 'protected', 'conservation', 'wildlife'],
41
+ tools: ['query_address', 'identify_features_at_point', 'get_environmental_data_for_area', 'get_environmental_data_near_point']
42
42
  },
43
43
  'Elevation & Terrain': {
44
44
  keywords: ['elevation', 'dem', 'terrain', 'contour', 'topography', 'slope', 'lidar', '3dep', 'relief', 'grading'],
@@ -61,12 +61,12 @@ const TOOL_CATEGORIES = {
61
61
  tools: ['generate_site_analysis', 'generate_constraints_report', 'generate_developability_report']
62
62
  },
63
63
  'Monitoring Stations': {
64
- keywords: ['station', 'monitoring', 'weather station', 'tide', 'groundwater', 'well', 'camera', 'sensor'],
64
+ keywords: ['station', 'monitoring', 'weather station', 'tide', 'groundwater', 'well', 'camera', 'sensor', 'find gage', 'find gauge', 'find stream gage', 'find stream gauge', 'stream gauges near', 'stream gages near', 'gauges within', 'gages within', 'nearby gage', 'nearby gauge'],
65
65
  tools: ['find_monitoring_stations', 'search_stations', 'get_station_types']
66
66
  },
67
67
  'Data Export': {
68
- keywords: ['export', 'download', 'shapefile', 'geojson', 'csv', 'kml', 'geopackage', 'geotiff', 'gis', 'cad'],
69
- tools: ['export_data', 'get_export_options', 'export_dem', 'export_contours', 'export_land_use', 'export_satellite_imagery']
68
+ keywords: ['export', 'download', 'shapefile', 'geojson', 'csv', 'kml', 'geopackage', 'gis', 'cad', 'as a shapefile', 'as geojson', 'as csv', 'as kml', 'export as', 'download as', 'save as', 'export data'],
69
+ tools: ['export_data', 'get_export_options']
70
70
  },
71
71
  'Land Use & Imagery': {
72
72
  keywords: ['land use', 'land cover', 'nlcd', 'satellite', 'imagery', 'aerial', 'naip', 'impervious surface', 'developed', 'forest'],
@@ -79,6 +79,22 @@ const TOOL_CATEGORIES = {
79
79
  'API Status': {
80
80
  keywords: ['status', 'health', 'api', 'available', 'working', 'service', 'down', 'outage'],
81
81
  tools: ['check_api_status', 'check_specific_api_status', 'check_rainfall_service_status']
82
+ },
83
+ 'Hazards & Risk': {
84
+ keywords: ['hazard', 'risk', 'earthquake', 'seismic', 'wildfire', 'fire', 'landslide', 'coastal vulnerability', 'nri', 'risk index', 'vulnerability', 'svi', 'social vulnerability', 'nfip', 'flood claims', 'flood insurance claims'],
85
+ tools: ['get_nri_risk', 'get_seismic_design_values', 'get_wildfire_perimeters', 'get_landslide_data', 'get_coastal_vulnerability', 'get_nfip_claims', 'get_social_vulnerability']
86
+ },
87
+ 'Energy & Solar': {
88
+ keywords: ['solar', 'energy', 'pvwatts', 'utility rate', 'electricity', 'ev charger', 'charging station', 'alternative fuel', 'renewable', 'irradiance', 'photovoltaic'],
89
+ tools: ['get_solar_resource', 'get_solar_estimate', 'get_utility_rates', 'get_alt_fuel_stations']
90
+ },
91
+ 'Infrastructure': {
92
+ keywords: ['hospital', 'fire station', 'school', 'police', 'law enforcement', 'power plant', 'ems', 'ambulance', 'airport', 'railroad', 'bridge', 'historic', 'national register'],
93
+ tools: ['get_hospitals', 'get_fire_stations', 'get_schools', 'get_power_plants', 'get_airports', 'get_railroad_crossings', 'get_bridges', 'get_historic_places']
94
+ },
95
+ 'Ecology & Biodiversity': {
96
+ keywords: ['species', 'biodiversity', 'bird', 'fish', 'essential fish habitat', 'occurrence', 'gbif', 'bison', 'marine', 'efh'],
97
+ tools: ['get_species_occurrences', 'get_fish_habitat']
82
98
  }
83
99
  };
84
100
 
package/src/index.js CHANGED
@@ -25,7 +25,7 @@ const legacyToolMap = new Map(tools.map(t => [t.name, t]));
25
25
  const server = new McpServer({
26
26
  name: 'geotap',
27
27
  version: '2.0.0',
28
- description: 'Access 37 US federal environmental and infrastructure data sources. Query flood zones, wetlands, soils, rainfall, watersheds, water quality, endangered species, elevation, land use, and more for any location in the United States.',
28
+ description: 'Access 71 US federal environmental and infrastructure data layers from 64+ agencies. Query flood zones, wetlands, soils, rainfall, watersheds, water quality, endangered species, elevation, land use, hazards, energy, infrastructure, transportation, and more for any location in the United States.',
29
29
  instructions: useLegacyTools
30
30
  ? `You have access to GeoTap with 85 individual tools. Use discover_tools to find the right one.`
31
31
  : `You have access to GeoTap, which provides real-time data from 37 US federal agencies (FEMA, USGS, NOAA, EPA, NRCS, USFWS, USACE, and more).
package/src/tools.js CHANGED
@@ -955,6 +955,251 @@ export const tools = [
955
955
  method: 'GET'
956
956
  },
957
957
 
958
+ // ═══════════════════════════════════════════════════════════════════
959
+ // HAZARDS & RISK — Natural hazards, risk indices, vulnerability
960
+ // ═══════════════════════════════════════════════════════════════════
961
+ {
962
+ name: 'get_earthquake_data',
963
+ description: `Get recent earthquake data from USGS near a location. Returns magnitude, depth, location, and time.`,
964
+ parameters: {
965
+ lat: z.number().describe('Latitude WGS84'),
966
+ lng: z.number().describe('Longitude WGS84'),
967
+ bbox: z.string().optional().describe('Bounding box "west,south,east,north"'),
968
+ },
969
+ endpoint: '/layers/stream_gauges/features',
970
+ method: 'GET',
971
+ _adapterDirect: 'usgs.getEarthquakes'
972
+ },
973
+ {
974
+ name: 'get_nri_risk',
975
+ description: `Get FEMA National Risk Index data for a county — overall risk score, expected annual loss, social vulnerability, and community resilience. Covers 18 natural hazard types including earthquakes, hurricanes, tornadoes, flooding, and wildfire.`,
976
+ parameters: {
977
+ bbox: z.string().describe('Bounding box "west,south,east,north"'),
978
+ geometry: z.enum(['none', 'simplified', 'full']).optional().describe('Geometry detail level (default: none)')
979
+ },
980
+ endpoint: '/layers/nri_risk/features',
981
+ method: 'GET'
982
+ },
983
+ {
984
+ name: 'get_social_vulnerability',
985
+ description: `Get CDC Social Vulnerability Index (SVI) data for census tracts — socioeconomic status, household composition, minority status, housing type. Overall SVI rank 0-1 (1 = most vulnerable).`,
986
+ parameters: {
987
+ bbox: z.string().describe('Bounding box "west,south,east,north"'),
988
+ geometry: z.enum(['none', 'simplified', 'full']).optional().describe('Geometry detail level (default: none)')
989
+ },
990
+ endpoint: '/layers/social_vulnerability/features',
991
+ method: 'GET'
992
+ },
993
+ {
994
+ name: 'get_seismic_design_values',
995
+ description: `Get USGS seismic design values (ASCE 7-22) for a building site — spectral acceleration, peak ground acceleration, site class adjustments. Essential for structural engineering.`,
996
+ parameters: {
997
+ lat: z.number().describe('Latitude WGS84'),
998
+ lng: z.number().describe('Longitude WGS84'),
999
+ },
1000
+ endpoint: '/layers/seismic_design/features',
1001
+ method: 'GET'
1002
+ },
1003
+ {
1004
+ name: 'get_wildfire_perimeters',
1005
+ description: `Get current and recent wildfire perimeters from NIFC — fire name, acres burned, containment percentage.`,
1006
+ parameters: {
1007
+ bbox: z.string().describe('Bounding box "west,south,east,north"'),
1008
+ geometry: z.enum(['none', 'simplified', 'full']).optional().describe('Geometry detail level (default: none)')
1009
+ },
1010
+ endpoint: '/layers/wildfire_perimeters/features',
1011
+ method: 'GET'
1012
+ },
1013
+ {
1014
+ name: 'get_landslide_data',
1015
+ description: `Get USGS landslide inventory data — historical landslide locations, types, movement class, and damage reports.`,
1016
+ parameters: {
1017
+ bbox: z.string().describe('Bounding box "west,south,east,north"'),
1018
+ geometry: z.enum(['none', 'simplified', 'full']).optional().describe('Geometry detail level (default: none)')
1019
+ },
1020
+ endpoint: '/layers/landslides/features',
1021
+ method: 'GET'
1022
+ },
1023
+ {
1024
+ name: 'get_nfip_claims',
1025
+ description: `Get FEMA NFIP flood insurance claims for a county — dates of loss, amounts paid, flood zones, occupancy types.`,
1026
+ parameters: {
1027
+ countyFips: z.string().describe('5-digit county FIPS code (e.g., "13051" for Chatham County GA)'),
1028
+ },
1029
+ endpoint: '/layers/nfip_claims/features',
1030
+ method: 'GET'
1031
+ },
1032
+ {
1033
+ name: 'get_coastal_vulnerability',
1034
+ description: `Get USGS Coastal Vulnerability Index — geomorphology, coastal slope, relative sea level rise, mean tide range, mean wave height, erosion rate.`,
1035
+ parameters: {
1036
+ bbox: z.string().describe('Bounding box "west,south,east,north"'),
1037
+ geometry: z.enum(['none', 'simplified', 'full']).optional().describe('Geometry detail level (default: none)')
1038
+ },
1039
+ endpoint: '/layers/coastal_vulnerability/features',
1040
+ method: 'GET'
1041
+ },
1042
+
1043
+ // ═══════════════════════════════════════════════════════════════════
1044
+ // ENERGY — Solar, wind, utility rates, EV charging, electricity
1045
+ // ═══════════════════════════════════════════════════════════════════
1046
+ {
1047
+ name: 'get_solar_resource',
1048
+ description: `Get NREL solar resource data for a location — direct normal irradiance (DNI), global horizontal irradiance (GHI), latitude tilt irradiance. Monthly and annual averages.`,
1049
+ parameters: {
1050
+ lat: z.number().describe('Latitude WGS84'),
1051
+ lng: z.number().describe('Longitude WGS84'),
1052
+ },
1053
+ endpoint: '/layers/solar_resource/features',
1054
+ method: 'GET'
1055
+ },
1056
+ {
1057
+ name: 'get_solar_estimate',
1058
+ description: `Get NREL PVWatts solar energy production estimate — annual/monthly AC energy output, capacity factor, solar radiation. Configurable system parameters.`,
1059
+ parameters: {
1060
+ lat: z.number().describe('Latitude WGS84'),
1061
+ lng: z.number().describe('Longitude WGS84'),
1062
+ system_capacity: z.number().optional().describe('System size in kW (default: 4)'),
1063
+ tilt: z.number().optional().describe('Panel tilt angle in degrees (default: 20)'),
1064
+ azimuth: z.number().optional().describe('Panel azimuth in degrees, 180=south (default: 180)'),
1065
+ },
1066
+ endpoint: '/layers/pvwatts/features',
1067
+ method: 'GET'
1068
+ },
1069
+ {
1070
+ name: 'get_utility_rates',
1071
+ description: `Get NREL utility rate data for a location — utility company name, residential/commercial/industrial electricity rates ($/kWh).`,
1072
+ parameters: {
1073
+ lat: z.number().describe('Latitude WGS84'),
1074
+ lng: z.number().describe('Longitude WGS84'),
1075
+ },
1076
+ endpoint: '/layers/utility_rates/features',
1077
+ method: 'GET'
1078
+ },
1079
+ {
1080
+ name: 'get_alt_fuel_stations',
1081
+ description: `Get DOE alternative fuel stations near a location — EV chargers, CNG, LPG, hydrogen, biodiesel. Includes network, connector types, and access info.`,
1082
+ parameters: {
1083
+ lat: z.number().describe('Latitude WGS84'),
1084
+ lng: z.number().describe('Longitude WGS84'),
1085
+ radius: z.number().optional().describe('Search radius in miles (default: 25)'),
1086
+ },
1087
+ endpoint: '/layers/alt_fuel_stations/features',
1088
+ method: 'GET'
1089
+ },
1090
+
1091
+ // ═══════════════════════════════════════════════════════════════════
1092
+ // INFRASTRUCTURE — HIFLD hospitals, fire stations, schools, etc.
1093
+ // ═══════════════════════════════════════════════════════════════════
1094
+ {
1095
+ name: 'get_hospitals',
1096
+ description: `Get hospitals near a location from HIFLD — name, address, beds, trauma level, helipad, ownership type.`,
1097
+ parameters: {
1098
+ bbox: z.string().describe('Bounding box "west,south,east,north"'),
1099
+ geometry: z.enum(['none', 'simplified', 'full']).optional().describe('Geometry detail level (default: none)')
1100
+ },
1101
+ endpoint: '/layers/hospitals/features',
1102
+ method: 'GET'
1103
+ },
1104
+ {
1105
+ name: 'get_fire_stations',
1106
+ description: `Get fire stations from HIFLD — name, address, type, status.`,
1107
+ parameters: {
1108
+ bbox: z.string().describe('Bounding box "west,south,east,north"'),
1109
+ geometry: z.enum(['none', 'simplified', 'full']).optional().describe('Geometry detail level (default: none)')
1110
+ },
1111
+ endpoint: '/layers/fire_stations/features',
1112
+ method: 'GET'
1113
+ },
1114
+ {
1115
+ name: 'get_schools',
1116
+ description: `Get public schools from HIFLD — name, enrollment, grade levels, teacher count.`,
1117
+ parameters: {
1118
+ bbox: z.string().describe('Bounding box "west,south,east,north"'),
1119
+ geometry: z.enum(['none', 'simplified', 'full']).optional().describe('Geometry detail level (default: none)')
1120
+ },
1121
+ endpoint: '/layers/schools/features',
1122
+ method: 'GET'
1123
+ },
1124
+ {
1125
+ name: 'get_power_plants',
1126
+ description: `Get power plants from HIFLD — name, fuel type, installed capacity (MW), technology type.`,
1127
+ parameters: {
1128
+ bbox: z.string().describe('Bounding box "west,south,east,north"'),
1129
+ geometry: z.enum(['none', 'simplified', 'full']).optional().describe('Geometry detail level (default: none)')
1130
+ },
1131
+ endpoint: '/layers/power_plants/features',
1132
+ method: 'GET'
1133
+ },
1134
+
1135
+ // ═══════════════════════════════════════════════════════════════════
1136
+ // ECOLOGY — Species, fish habitat, cropland
1137
+ // ═══════════════════════════════════════════════════════════════════
1138
+ {
1139
+ name: 'get_species_occurrences',
1140
+ description: `Get species occurrence records from GBIF (replaced USGS BISON) — scientific name, kingdom, family, observation date, basis of record.`,
1141
+ parameters: {
1142
+ bbox: z.string().describe('Bounding box "west,south,east,north"'),
1143
+ limit: z.number().optional().describe('Max records (default: 300)'),
1144
+ },
1145
+ endpoint: '/layers/species_occurrences/features',
1146
+ method: 'GET'
1147
+ },
1148
+ {
1149
+ name: 'get_fish_habitat',
1150
+ description: `Get NOAA Essential Fish Habitat data — species, habitat type, life stage, fishery management council.`,
1151
+ parameters: {
1152
+ bbox: z.string().describe('Bounding box "west,south,east,north"'),
1153
+ geometry: z.enum(['none', 'simplified', 'full']).optional().describe('Geometry detail level (default: none)')
1154
+ },
1155
+ endpoint: '/layers/fish_habitat/features',
1156
+ method: 'GET'
1157
+ },
1158
+
1159
+ // ═══════════════════════════════════════════════════════════════════
1160
+ // TRANSPORTATION — Airports, railroads, bridges
1161
+ // ═══════════════════════════════════════════════════════════════════
1162
+ {
1163
+ name: 'get_airports',
1164
+ description: `Get FAA airports and aviation facilities from NTAD — facility name, type, ownership, operations counts, elevation.`,
1165
+ parameters: {
1166
+ bbox: z.string().describe('Bounding box "west,south,east,north"'),
1167
+ geometry: z.enum(['none', 'simplified', 'full']).optional().describe('Geometry detail level (default: none)')
1168
+ },
1169
+ endpoint: '/layers/airports/features',
1170
+ method: 'GET'
1171
+ },
1172
+ {
1173
+ name: 'get_railroad_crossings',
1174
+ description: `Get FRA highway-rail grade crossings — railroad company, warning devices, trains per day, crash/fatality/injury data.`,
1175
+ parameters: {
1176
+ bbox: z.string().describe('Bounding box "west,south,east,north"'),
1177
+ geometry: z.enum(['none', 'simplified', 'full']).optional().describe('Geometry detail level (default: none)')
1178
+ },
1179
+ endpoint: '/layers/railroad_crossings/features',
1180
+ method: 'GET'
1181
+ },
1182
+ {
1183
+ name: 'get_bridges',
1184
+ description: `Get DOT National Bridge Inventory data from NTAD — year built, condition ratings (0-9 scale), sufficiency rating (0-100), average daily traffic, structural details.`,
1185
+ parameters: {
1186
+ bbox: z.string().describe('Bounding box "west,south,east,north"'),
1187
+ geometry: z.enum(['none', 'simplified', 'full']).optional().describe('Geometry detail level (default: none)')
1188
+ },
1189
+ endpoint: '/layers/bridges/features',
1190
+ method: 'GET'
1191
+ },
1192
+ {
1193
+ name: 'get_historic_places',
1194
+ description: `Get National Register of Historic Places listings from NPS — name, significance level, certification date, category, period of significance.`,
1195
+ parameters: {
1196
+ bbox: z.string().describe('Bounding box "west,south,east,north"'),
1197
+ geometry: z.enum(['none', 'simplified', 'full']).optional().describe('Geometry detail level (default: none)')
1198
+ },
1199
+ endpoint: '/layers/historic_places/features',
1200
+ method: 'GET'
1201
+ },
1202
+
958
1203
  // ═══════════════════════════════════════════════════════════════════
959
1204
  // HEALTH, STATUS & API INFO
960
1205
  // ═══════════════════════════════════════════════════════════════════
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Prompt Audit — test every question from the developers page against discover_tools
4
+ * and verify tool routing for the consolidated v2.0 MCP server.
5
+ */
6
+
7
+ import { discoverTools } from '../src/discoverTools.js';
8
+ import { consolidatedTools } from '../src/consolidatedTools.js';
9
+
10
+ // Build reverse map: legacy tool name → consolidated tool + action
11
+ const reverseMap = new Map();
12
+ for (const ctool of consolidatedTools) {
13
+ for (const [action, legacyName] of Object.entries(ctool._actionMap)) {
14
+ reverseMap.set(legacyName, { tool: ctool.name, action });
15
+ }
16
+ }
17
+
18
+ // All prompts from the developers page
19
+ const PROMPT_CATEGORIES = [
20
+ {
21
+ category: 'Flood Risk & FEMA',
22
+ prompts: [
23
+ 'What flood zone is 123 Main St, Houston TX in?',
24
+ 'Is this property in a floodway or just a flood zone?',
25
+ 'What FEMA flood zones are near 30.27, -97.74?',
26
+ "What's the base flood elevation at 123 Oak Ave, Tampa FL?",
27
+ ],
28
+ expectedTools: ['query_location', 'query_location', 'query_location', 'query_location'],
29
+ expectedActions: ['address', 'address', 'nearby', 'address'],
30
+ },
31
+ {
32
+ category: 'Rainfall & Hydrology',
33
+ prompts: [
34
+ "What's the 100-year, 24-hour rainfall in Austin, TX?",
35
+ 'Get Atlas 14 precipitation data for my site at 35.2, -80.8',
36
+ 'Calculate the curve number for this watershed',
37
+ "What's the time of concentration for a 50-acre drainage area?",
38
+ 'Calculate peak discharge for a 25-year storm using the SCS method',
39
+ ],
40
+ expectedTools: ['get_rainfall', 'get_rainfall', 'get_hydrology', 'get_hydrology', 'get_hydrology'],
41
+ expectedActions: ['atlas14', 'atlas14', 'analyze_cn', 'analyze', 'analyze'],
42
+ },
43
+ {
44
+ category: 'Wetlands & Water',
45
+ prompts: [
46
+ 'Are there any wetlands within 500ft of this property?',
47
+ 'What type of wetlands are at 29.95, -90.07?',
48
+ 'Is this stream listed as impaired under the Clean Water Act?',
49
+ 'What are the water quality issues near 40.7, -74.0?',
50
+ 'Find monitoring stations within 10 miles of Philadelphia',
51
+ ],
52
+ expectedTools: ['query_location', 'query_location', 'get_water_quality', 'get_water_quality', 'find_stations'],
53
+ expectedActions: ['nearby', 'point', 'assessment', 'assessment', 'search_area'],
54
+ },
55
+ {
56
+ category: 'Soils & Terrain',
57
+ prompts: [
58
+ 'What soil type is at this location and does it drain well?',
59
+ 'Is the soil hydric at 33.45, -84.39?',
60
+ "What soil type and drainage class is at my project site?",
61
+ "What's the elevation and slope at 38.9, -77.04?",
62
+ ],
63
+ expectedTools: ['query_location', 'query_location', 'query_location', 'get_elevation'],
64
+ expectedActions: ['point', 'point', 'point', 'stats'],
65
+ },
66
+ {
67
+ category: 'Environmental Contamination',
68
+ prompts: [
69
+ 'Are there any Superfund sites near 40.7, -74.0?',
70
+ 'Check for brownfields within 1 mile of this address',
71
+ 'Any underground storage tanks near my property?',
72
+ 'Are there any contamination sites or storage tanks near my property?',
73
+ ],
74
+ expectedTools: ['query_location', 'query_location', 'query_location', 'query_location'],
75
+ expectedActions: ['nearby', 'nearby', 'nearby', 'nearby'],
76
+ },
77
+ {
78
+ category: 'Watershed & Site Analysis',
79
+ prompts: [
80
+ 'Delineate the watershed from this pour point',
81
+ 'What are the basin characteristics for this watershed?',
82
+ 'Run a full site developability assessment for this parcel',
83
+ 'What environmental constraints exist at this property?',
84
+ 'Check if this site has critical habitat or protected lands',
85
+ ],
86
+ expectedTools: ['get_watershed', 'get_watershed', 'generate_report', 'generate_report', 'query_location'],
87
+ expectedActions: ['delineate', 'characteristics', 'site_analysis', 'constraints', 'address'],
88
+ },
89
+ {
90
+ category: 'Stream Gage Analysis',
91
+ prompts: [
92
+ 'Run a flood frequency analysis on USGS gage 08158000',
93
+ "What's the flow duration curve for this stream gage?",
94
+ 'Find storm events above 1000 cfs at this gage in the last 5 years',
95
+ 'What are the published USGS statistics for gage 01646500?',
96
+ ],
97
+ expectedTools: ['analyze_gage', 'analyze_gage', 'analyze_gage', 'analyze_gage'],
98
+ expectedActions: ['flood_frequency', 'flow_duration', 'storm_events', 'published_stats'],
99
+ },
100
+ {
101
+ category: 'Ungaged Flow Estimation',
102
+ prompts: [
103
+ 'Estimate flood flows at this ungaged site using regional regression',
104
+ 'Find similar gauged watersheds to compare against my site',
105
+ 'Recommend an index gage for transferring flow stats to my ungaged site',
106
+ ],
107
+ expectedTools: ['estimate_ungaged', 'estimate_ungaged', 'estimate_ungaged'],
108
+ expectedActions: ['flood_frequency', 'find_similar', 'recommend_index'],
109
+ },
110
+ {
111
+ category: 'Data Export',
112
+ prompts: [
113
+ 'Export flood zones as a shapefile for this area',
114
+ 'Download the soil data as GeoJSON for this polygon',
115
+ 'Export wetlands data as CSV for my project area',
116
+ 'Export all environmental layers as KML for Google Earth',
117
+ ],
118
+ expectedTools: ['export_data', 'export_data', 'export_data', 'export_data'],
119
+ expectedActions: ['export', 'export', 'export', 'export'],
120
+ },
121
+ ];
122
+
123
+ console.log('═══════════════════════════════════════════════════════════════');
124
+ console.log(' PROMPT AUDIT — Developers Page Questions vs MCP v2.0');
125
+ console.log('═══════════════════════════════════════════════════════════════\n');
126
+
127
+ let totalQuestions = 0;
128
+ let discoveryHits = 0;
129
+ let discoveryMisses = 0;
130
+ let routingCorrect = 0;
131
+ let routingWrong = 0;
132
+ const issues = [];
133
+
134
+ for (const cat of PROMPT_CATEGORIES) {
135
+ console.log(`\n▸ ${cat.category}`);
136
+ console.log('─'.repeat(60));
137
+
138
+ for (let i = 0; i < cat.prompts.length; i++) {
139
+ totalQuestions++;
140
+ const prompt = cat.prompts[i];
141
+ const expectedTool = cat.expectedTools[i];
142
+ const expectedAction = cat.expectedActions[i];
143
+
144
+ // Run through discover_tools
145
+ const result = discoverTools(prompt, 5);
146
+ const topLegacy = result.recommendedTools[0]?.name || '(none)';
147
+ const topScore = result.recommendedTools[0]?.relevanceScore || 0;
148
+
149
+ // Map legacy recommendation → consolidated tool
150
+ const mapped = reverseMap.get(topLegacy);
151
+ const actualTool = mapped?.tool || '???';
152
+ const actualAction = mapped?.action || '???';
153
+
154
+ // Check if discovery found a relevant result
155
+ const hasDiscovery = result.recommendedTools.length > 0 && topScore >= 1;
156
+
157
+ // Check if routing is correct (matches expected)
158
+ const toolMatch = actualTool === expectedTool;
159
+ // Action match is looser — some questions could reasonably go to different actions
160
+ const actionMatch = actualAction === expectedAction;
161
+ const isCorrect = toolMatch;
162
+
163
+ if (hasDiscovery) discoveryHits++;
164
+ else discoveryMisses++;
165
+ if (isCorrect) routingCorrect++;
166
+ else routingWrong++;
167
+
168
+ const status = isCorrect ? '✓' : '✗';
169
+ const actionStatus = actionMatch ? '' : ` (expected action: ${expectedAction})`;
170
+ console.log(` ${status} "${prompt}"`);
171
+ console.log(` → discover: ${topLegacy} (score: ${topScore})`);
172
+ console.log(` → maps to: ${actualTool}.${actualAction}${actionStatus}`);
173
+ if (!toolMatch) {
174
+ console.log(` ⚠ EXPECTED: ${expectedTool}.${expectedAction}`);
175
+ issues.push({ prompt, expected: `${expectedTool}.${expectedAction}`, got: `${actualTool}.${actualAction}`, category: cat.category });
176
+ }
177
+ }
178
+ }
179
+
180
+ console.log('\n═══════════════════════════════════════════════════════════════');
181
+ console.log(' SUMMARY');
182
+ console.log('═══════════════════════════════════════════════════════════════');
183
+ console.log(` Total questions: ${totalQuestions}`);
184
+ console.log(` Discovery hit rate: ${discoveryHits}/${totalQuestions} (${Math.round(discoveryHits/totalQuestions*100)}%)`);
185
+ console.log(` Correct tool: ${routingCorrect}/${totalQuestions} (${Math.round(routingCorrect/totalQuestions*100)}%)`);
186
+ console.log(` Wrong tool: ${routingWrong}/${totalQuestions}`);
187
+
188
+ if (issues.length > 0) {
189
+ console.log(`\n ⚠ ISSUES (${issues.length}):`);
190
+ for (const iss of issues) {
191
+ console.log(` [${iss.category}] "${iss.prompt}"`);
192
+ console.log(` Expected: ${iss.expected}, Got: ${iss.got}`);
193
+ }
194
+ }
195
+
196
+ console.log('\n═══════════════════════════════════════════════════════════════');
197
+ console.log(' CONSOLIDATED TOOL COVERAGE');
198
+ console.log('═══════════════════════════════════════════════════════════════');
199
+
200
+ // Check which consolidated tools are exercised by the prompts
201
+ const usedTools = new Set();
202
+ for (const cat of PROMPT_CATEGORIES) {
203
+ for (const t of cat.expectedTools) usedTools.add(t);
204
+ }
205
+ for (const ctool of consolidatedTools) {
206
+ const used = usedTools.has(ctool.name);
207
+ console.log(` ${used ? '✓' : '○'} ${ctool.name} (${Object.keys(ctool._actionMap).length} actions)`);
208
+ }
209
+
210
+ console.log('\n Tools NOT exercised by any developer page question:');
211
+ for (const ctool of consolidatedTools) {
212
+ if (!usedTools.has(ctool.name)) {
213
+ console.log(` - ${ctool.name}`);
214
+ }
215
+ }
216
+ console.log();