geotap-mcp-server 2.0.0 → 2.2.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 +1 -1
- package/src/consolidatedTools.js +126 -4
- package/src/discoverTools.js +51 -13
- package/src/index.js +7 -3
- package/src/paramNormalize.js +60 -1
- package/src/tools.js +245 -0
- package/tests/Spec_Comprehensive_Test_Suite.md +1203 -0
- package/tests/comprehensive-test.js +865 -0
- package/tests/prompt-audit.js +216 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "geotap-mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.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",
|
package/src/consolidatedTools.js
CHANGED
|
@@ -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
|
|
29
|
+
- "list_layers" — List 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: {
|
|
@@ -76,7 +76,7 @@ Actions:
|
|
|
76
76
|
- "hyetograph" — Generate a design storm hyetograph (rainfall over time) for hydrologic modeling.
|
|
77
77
|
- "export_hyetograph" — Export hyetograph as CSV/JSON for HEC-HMS, SWMM, etc.
|
|
78
78
|
- "distributions" — List available rainfall distribution types (SCS Type I/IA/II/III, Huff, etc.).
|
|
79
|
-
- "recommend_distribution" — Determine which distribution type applies at a location.
|
|
79
|
+
- "recommend_distribution" — Determine which distribution type applies at a location. Note: this uses coastal/inland boundaries. For SCS design storms, prefer get_hydrology(action=distribution_for_location) which uses NRCS regional mapping.
|
|
80
80
|
- "climate_scenarios" — List available SSP scenarios and time horizons.
|
|
81
81
|
- "climate_factors" — Get climate change adjustment multipliers for a location.
|
|
82
82
|
- "climate_projection" — Apply climate change projections to Atlas 14 data.
|
|
@@ -396,7 +396,7 @@ Actions:
|
|
|
396
396
|
// ═══════════════════════════════════════════════════════════════════
|
|
397
397
|
{
|
|
398
398
|
name: 'export_data',
|
|
399
|
-
description: `Export environmental data layers to GIS formats (GeoJSON, Shapefile,
|
|
399
|
+
description: `Export environmental data layers to GIS formats (GeoJSON, Shapefile, CSV).
|
|
400
400
|
|
|
401
401
|
Actions:
|
|
402
402
|
- "export" — Export layers to a file format. Supports CRS transformation and clipping.
|
|
@@ -406,7 +406,7 @@ Actions:
|
|
|
406
406
|
action: z.enum(['export', 'options', 'status'])
|
|
407
407
|
.describe('Which export operation'),
|
|
408
408
|
layers: z.array(z.string()).optional().describe('Layer names to export'),
|
|
409
|
-
format: z.enum(['geojson', 'shapefile', '
|
|
409
|
+
format: z.enum(['geojson', 'shapefile', 'csv']).optional().describe('Output format'),
|
|
410
410
|
crs: z.string().optional().describe('Target CRS (e.g., "EPSG:4326")'),
|
|
411
411
|
geometry: z.any().optional().describe('GeoJSON geometry to clip export area'),
|
|
412
412
|
options: z.any().optional().describe('Additional options: {dem, satellite, nlcd, contours}'),
|
|
@@ -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: name, FAA code, city, state.
|
|
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: name, NRIS reference number.`,
|
|
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
|
];
|
package/src/discoverTools.js
CHANGED
|
@@ -7,13 +7,27 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { tools } from './tools.js';
|
|
10
|
+
import { consolidatedTools } from './consolidatedTools.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Build reverse mapping: legacy tool name → consolidated "tool(action=X)" format.
|
|
14
|
+
* Used to translate discover_tools results when running in consolidated mode.
|
|
15
|
+
*/
|
|
16
|
+
const legacyToConsolidated = {};
|
|
17
|
+
for (const ctool of consolidatedTools) {
|
|
18
|
+
for (const [action, legacyName] of Object.entries(ctool._actionMap)) {
|
|
19
|
+
legacyToConsolidated[legacyName] = { tool: ctool.name, action };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const useLegacyTools = process.env.GEOTAP_LEGACY_TOOLS === 'true';
|
|
10
24
|
|
|
11
25
|
/**
|
|
12
26
|
* Tool categories with keywords for matching.
|
|
13
27
|
*/
|
|
14
28
|
const TOOL_CATEGORIES = {
|
|
15
29
|
'Getting Started': {
|
|
16
|
-
keywords: ['start', 'begin', 'address', 'location', 'what is', 'tell me about', 'lookup', 'search address', 'find address'],
|
|
30
|
+
keywords: ['start', 'begin', 'address', 'location', 'what is', 'tell me about', 'lookup', 'search address', 'find address', 'hydric', 'soil type', 'what soil'],
|
|
17
31
|
tools: ['query_address', 'identify_features_at_point', 'geocode_address']
|
|
18
32
|
},
|
|
19
33
|
'Flood & FEMA': {
|
|
@@ -25,20 +39,20 @@ const TOOL_CATEGORIES = {
|
|
|
25
39
|
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
40
|
},
|
|
27
41
|
'Watershed & Hydrology': {
|
|
28
|
-
keywords: ['watershed', 'drainage', 'basin', 'delineate', '
|
|
42
|
+
keywords: ['watershed', 'drainage', 'basin', 'delineate', 'runoff', 'streamstats', 'catchment', 'time of concentration', 'hydrology analysis', 'hydrologic analysis'],
|
|
29
43
|
tools: ['delineate_watershed', 'get_watershed_characteristics', 'get_flow_statistics', 'analyze_hydrology', 'get_flowlines']
|
|
30
44
|
},
|
|
31
|
-
'Curve Number
|
|
32
|
-
keywords: ['curve number', 'cn', '
|
|
33
|
-
tools: ['analyze_curve_numbers', 'lookup_curve_number', 'get_curve_number_tables']
|
|
45
|
+
'Curve Number': {
|
|
46
|
+
keywords: ['curve number', 'cn value', 'runoff coefficient', 'scs method', 'nrcs method', 'infiltration rate', 'pervious', 'impervious', 'peak flow', 'peak discharge', 'hydrology'],
|
|
47
|
+
tools: ['analyze_curve_numbers', 'lookup_curve_number', 'get_curve_number_tables', 'analyze_hydrology']
|
|
34
48
|
},
|
|
35
49
|
'Water Quality': {
|
|
36
|
-
keywords: ['water quality', 'impairment', 'impaired', 'pollution', 'pollutant', 'tmdl', '303d', 'attains', '
|
|
50
|
+
keywords: ['water quality', 'impairment', 'impaired', 'pollution', 'pollutant', 'tmdl', '303d', 'attains', 'clean water act'],
|
|
37
51
|
tools: ['get_water_quality', 'get_water_impairments', 'get_watershed_for_point', 'get_watershed_water_quality']
|
|
38
52
|
},
|
|
39
53
|
'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']
|
|
54
|
+
keywords: ['wetland', 'wetlands near', 'nwi', 'endangered', 'species', 'habitat', 'critical habitat', 'protected', 'conservation', 'wildlife'],
|
|
55
|
+
tools: ['query_address', 'identify_features_at_point', 'get_environmental_data_for_area', 'get_environmental_data_near_point']
|
|
42
56
|
},
|
|
43
57
|
'Elevation & Terrain': {
|
|
44
58
|
keywords: ['elevation', 'dem', 'terrain', 'contour', 'topography', 'slope', 'lidar', '3dep', 'relief', 'grading'],
|
|
@@ -61,12 +75,12 @@ const TOOL_CATEGORIES = {
|
|
|
61
75
|
tools: ['generate_site_analysis', 'generate_constraints_report', 'generate_developability_report']
|
|
62
76
|
},
|
|
63
77
|
'Monitoring Stations': {
|
|
64
|
-
keywords: ['station', 'monitoring', 'weather station', 'tide', 'groundwater', 'well', 'camera', 'sensor'],
|
|
78
|
+
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
79
|
tools: ['find_monitoring_stations', 'search_stations', 'get_station_types']
|
|
66
80
|
},
|
|
67
81
|
'Data Export': {
|
|
68
|
-
keywords: ['export', 'download', 'shapefile', 'geojson', 'csv', 'kml', 'geopackage', '
|
|
69
|
-
tools: ['export_data', 'get_export_options'
|
|
82
|
+
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'],
|
|
83
|
+
tools: ['export_data', 'get_export_options']
|
|
70
84
|
},
|
|
71
85
|
'Land Use & Imagery': {
|
|
72
86
|
keywords: ['land use', 'land cover', 'nlcd', 'satellite', 'imagery', 'aerial', 'naip', 'impervious surface', 'developed', 'forest'],
|
|
@@ -79,6 +93,22 @@ const TOOL_CATEGORIES = {
|
|
|
79
93
|
'API Status': {
|
|
80
94
|
keywords: ['status', 'health', 'api', 'available', 'working', 'service', 'down', 'outage'],
|
|
81
95
|
tools: ['check_api_status', 'check_specific_api_status', 'check_rainfall_service_status']
|
|
96
|
+
},
|
|
97
|
+
'Hazards & Risk': {
|
|
98
|
+
keywords: ['hazard', 'risk', 'earthquake', 'seismic', 'wildfire', 'fire', 'landslide', 'coastal vulnerability', 'nri', 'risk index', 'vulnerability', 'svi', 'social vulnerability', 'nfip', 'flood claims', 'flood insurance claims'],
|
|
99
|
+
tools: ['get_nri_risk', 'get_seismic_design_values', 'get_wildfire_perimeters', 'get_landslide_data', 'get_coastal_vulnerability', 'get_nfip_claims', 'get_social_vulnerability']
|
|
100
|
+
},
|
|
101
|
+
'Energy & Solar': {
|
|
102
|
+
keywords: ['solar', 'energy', 'pvwatts', 'utility rate', 'electricity', 'ev charger', 'charging station', 'alternative fuel', 'renewable', 'irradiance', 'photovoltaic'],
|
|
103
|
+
tools: ['get_solar_resource', 'get_solar_estimate', 'get_utility_rates', 'get_alt_fuel_stations']
|
|
104
|
+
},
|
|
105
|
+
'Infrastructure': {
|
|
106
|
+
keywords: ['hospital', 'fire station', 'school', 'police', 'law enforcement', 'power plant', 'ems', 'ambulance', 'airport', 'railroad', 'bridge', 'historic', 'national register'],
|
|
107
|
+
tools: ['get_hospitals', 'get_fire_stations', 'get_schools', 'get_power_plants', 'get_airports', 'get_railroad_crossings', 'get_bridges', 'get_historic_places']
|
|
108
|
+
},
|
|
109
|
+
'Ecology & Biodiversity': {
|
|
110
|
+
keywords: ['species', 'biodiversity', 'bird', 'fish', 'essential fish habitat', 'occurrence', 'gbif', 'bison', 'marine', 'efh'],
|
|
111
|
+
tools: ['get_species_occurrences', 'get_fish_habitat']
|
|
82
112
|
}
|
|
83
113
|
};
|
|
84
114
|
|
|
@@ -140,8 +170,14 @@ export function discoverTools(question, maxResults = 5) {
|
|
|
140
170
|
}
|
|
141
171
|
}
|
|
142
172
|
|
|
173
|
+
// In consolidated mode, translate legacy name to consolidated tool+action format
|
|
174
|
+
const consolidated = !useLegacyTools ? legacyToConsolidated[tool.name] : null;
|
|
175
|
+
const displayName = consolidated
|
|
176
|
+
? `${consolidated.tool}(action="${consolidated.action}")`
|
|
177
|
+
: tool.name;
|
|
178
|
+
|
|
143
179
|
return {
|
|
144
|
-
name:
|
|
180
|
+
name: displayName,
|
|
145
181
|
description: tool.description.split('.')[0] + '.', // First sentence only
|
|
146
182
|
method: tool.method,
|
|
147
183
|
parameters: params,
|
|
@@ -167,7 +203,9 @@ export function discoverTools(question, maxResults = 5) {
|
|
|
167
203
|
allCategories: Object.keys(TOOL_CATEGORIES),
|
|
168
204
|
hint: results.length > 0
|
|
169
205
|
? `Start with "${results[0].name}" — it's the best match for your question.`
|
|
170
|
-
:
|
|
206
|
+
: useLegacyTools
|
|
207
|
+
? 'No strong matches found. Try query_address for location-based questions, or list_data_layers to see available data.'
|
|
208
|
+
: 'No strong matches found. Try query_location(action="address") for location-based questions, or query_location(action="list_layers") to see available data.',
|
|
171
209
|
totalToolsAvailable: tools.length
|
|
172
210
|
};
|
|
173
211
|
}
|
package/src/index.js
CHANGED
|
@@ -25,12 +25,12 @@ 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
|
|
28
|
+
description: 'Access 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).
|
|
32
32
|
|
|
33
|
-
TOOL OVERVIEW (
|
|
33
|
+
TOOL OVERVIEW (16 tools + 2 meta-tools):
|
|
34
34
|
1. query_location — Start here. Query environmental data by address, coordinates, bbox, polygon, or radius.
|
|
35
35
|
2. get_rainfall — NOAA Atlas 14 precipitation, IDF curves, hyetographs, climate projections.
|
|
36
36
|
3. get_watershed — Watershed delineation, flow statistics, flowlines, HUC boundaries, FIRM panels.
|
|
@@ -40,9 +40,13 @@ TOOL OVERVIEW (12 tools + 2 meta-tools):
|
|
|
40
40
|
7. analyze_gage — USGS stream gage analysis: flood frequency, flow duration, storm events.
|
|
41
41
|
8. estimate_ungaged — Flow estimation at ungaged sites: regression, similarity, transfer methods.
|
|
42
42
|
9. generate_report — Site analysis, constraints, and developability reports with scoring.
|
|
43
|
-
10. export_data — Export layers to GeoJSON, Shapefile,
|
|
43
|
+
10. export_data — Export layers to GeoJSON, Shapefile, CSV.
|
|
44
44
|
11. find_stations — Find monitoring stations (USGS, NOAA) and analyze waterway permit requirements.
|
|
45
45
|
12. check_status — API health and federal data source connectivity.
|
|
46
|
+
13. get_hazards — FEMA risk index, seismic design values, wildfires, landslides, coastal vulnerability, flood insurance claims, social vulnerability.
|
|
47
|
+
14. get_energy — Solar resource, PVWatts production estimates, utility rates, EV charging stations.
|
|
48
|
+
15. get_infrastructure — Hospitals, fire stations, schools, power plants, airports, railroad crossings, bridges, historic places.
|
|
49
|
+
16. get_ecology — Species occurrences, essential fish habitat.
|
|
46
50
|
|
|
47
51
|
Every tool uses an "action" parameter to select the specific operation. Read the tool description to see available actions.
|
|
48
52
|
|
package/src/paramNormalize.js
CHANGED
|
@@ -112,7 +112,66 @@ export function normalizeParams(toolName, params) {
|
|
|
112
112
|
p.geometry = 'none';
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
// ── 3.
|
|
115
|
+
// ── 3. Ungaged estimation: map `parameters` → `basinCharacteristics` ──
|
|
116
|
+
// The MCP tool accepts friendly names (drainageArea, meanBasinSlope)
|
|
117
|
+
// but the backend NSS API expects USGS codes (DRNAREA, BSLDEM)
|
|
118
|
+
if (toolName === 'estimate_ungaged_flood_frequency' ||
|
|
119
|
+
toolName === 'estimate_all_ungaged_statistics') {
|
|
120
|
+
const paramObj = p.parameters || p.characteristics || {};
|
|
121
|
+
if (Object.keys(paramObj).length > 0) {
|
|
122
|
+
// Map friendly names → USGS NSS parameter codes
|
|
123
|
+
const PARAM_MAP = {
|
|
124
|
+
drainageArea: 'DRNAREA',
|
|
125
|
+
contributingDrainageArea: 'CONTDA',
|
|
126
|
+
meanAnnualPrecipitation: 'PRECIP',
|
|
127
|
+
meanBasinSlope: 'BSLDEM',
|
|
128
|
+
percentForest: 'FOREST',
|
|
129
|
+
percentStorage: 'STORAGE',
|
|
130
|
+
percentImpervious: 'IMPERV',
|
|
131
|
+
meanBasinElevation: 'ELEV',
|
|
132
|
+
basinLength: 'BSHAPE',
|
|
133
|
+
annualRunoff: 'RUNOFF',
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const basinChars = {};
|
|
137
|
+
for (const [key, value] of Object.entries(paramObj)) {
|
|
138
|
+
// Use mapped name if available, otherwise pass through as-is
|
|
139
|
+
// (allows users to send DRNAREA directly too)
|
|
140
|
+
const mappedKey = PARAM_MAP[key] || key;
|
|
141
|
+
basinChars[mappedKey] = Number(value);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
p.basinCharacteristics = basinChars;
|
|
145
|
+
delete p.parameters;
|
|
146
|
+
delete p.characteristics;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Also map for find_similar tools
|
|
151
|
+
if (toolName === 'find_similar_watersheds' ||
|
|
152
|
+
toolName === 'find_similar_watersheds_with_stats') {
|
|
153
|
+
const charObj = p.characteristics || p.parameters || {};
|
|
154
|
+
if (Object.keys(charObj).length > 0) {
|
|
155
|
+
const PARAM_MAP = {
|
|
156
|
+
drainageArea: 'DRNAREA',
|
|
157
|
+
contributingDrainageArea: 'CONTDA',
|
|
158
|
+
meanAnnualPrecipitation: 'PRECIP',
|
|
159
|
+
meanBasinSlope: 'BSLDEM',
|
|
160
|
+
percentForest: 'FOREST',
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const mapped = {};
|
|
164
|
+
for (const [key, value] of Object.entries(charObj)) {
|
|
165
|
+
const mappedKey = PARAM_MAP[key] || key;
|
|
166
|
+
mapped[mappedKey] = Number(value);
|
|
167
|
+
}
|
|
168
|
+
p.basinCharacteristics = mapped;
|
|
169
|
+
delete p.characteristics;
|
|
170
|
+
delete p.parameters;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ── 4. Strip undefined/null optional params ──────────────────────
|
|
116
175
|
for (const [key, value] of Object.entries(p)) {
|
|
117
176
|
if (value === undefined || value === null) {
|
|
118
177
|
delete p[key];
|
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
|
// ═══════════════════════════════════════════════════════════════════
|