geotap-mcp-server 2.1.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "geotap-mcp-server",
3
- "version": "2.1.0",
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",
@@ -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 71 available data layers.
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, KML, CSV, GeoPackage).
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', 'kml', 'csv', 'geopackage']).optional().describe('Output format'),
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}'),
@@ -555,10 +555,10 @@ Actions:
555
555
  - "fire_stations" — Fire stations: type, status.
556
556
  - "schools" — Public schools: enrollment, grade levels, teacher count.
557
557
  - "power_plants" — Power plants: fuel type, installed capacity (MW).
558
- - "airports" — FAA airports: facility type, ownership, operations counts.
558
+ - "airports" — FAA airports: name, FAA code, city, state.
559
559
  - "railroad_crossings" — FRA highway-rail crossings: warning devices, trains/day, crash data.
560
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.`,
561
+ - "historic_places" — National Register of Historic Places: name, NRIS reference number.`,
562
562
  parameters: {
563
563
  action: z.enum(['hospitals', 'fire_stations', 'schools', 'power_plants', 'airports', 'railroad_crossings', 'bridges', 'historic_places'])
564
564
  .describe('Which infrastructure data to query'),
@@ -7,6 +7,20 @@
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.
@@ -156,8 +170,14 @@ export function discoverTools(question, maxResults = 5) {
156
170
  }
157
171
  }
158
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
+
159
179
  return {
160
- name: tool.name,
180
+ name: displayName,
161
181
  description: tool.description.split('.')[0] + '.', // First sentence only
162
182
  method: tool.method,
163
183
  parameters: params,
@@ -183,7 +203,9 @@ export function discoverTools(question, maxResults = 5) {
183
203
  allCategories: Object.keys(TOOL_CATEGORIES),
184
204
  hint: results.length > 0
185
205
  ? `Start with "${results[0].name}" — it's the best match for your question.`
186
- : 'No strong matches found. Try query_address for location-based questions, or list_data_layers to see available data.',
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.',
187
209
  totalToolsAvailable: tools.length
188
210
  };
189
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 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.',
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 (12 tools + 2 meta-tools):
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, KML, CSV, GeoPackage.
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
 
@@ -112,7 +112,66 @@ export function normalizeParams(toolName, params) {
112
112
  p.geometry = 'none';
113
113
  }
114
114
 
115
- // ── 3. Strip undefined/null optional params ──────────────────────
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];