geotap-mcp-server 1.2.1 → 2.0.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 +5 -2
- package/src/api.js +91 -1
- package/src/consolidatedTools.js +480 -0
- package/src/discoverTools.js +173 -0
- package/src/index.js +308 -59
- package/src/latLngHelper.js +137 -0
- package/src/llms.txt +102 -0
- package/src/paramNormalize.js +123 -0
- package/src/responseCap.js +233 -0
- package/src/sources.js +4 -0
- package/src/summaries.js +396 -0
- package/tests/unit-tests.js +594 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "geotap-mcp-server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.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",
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node src/index.js",
|
|
12
|
-
"dev": "node --watch src/index.js"
|
|
12
|
+
"dev": "node --watch src/index.js",
|
|
13
|
+
"test": "node tests/unit-tests.js",
|
|
14
|
+
"test:verbose": "node tests/unit-tests.js --verbose",
|
|
15
|
+
"test:integration": "node tests/workflow-tests.js"
|
|
13
16
|
},
|
|
14
17
|
"keywords": [
|
|
15
18
|
"mcp",
|
package/src/api.js
CHANGED
|
@@ -1,6 +1,95 @@
|
|
|
1
1
|
const BASE_URL = process.env.GEOTAP_API_URL || 'https://geotapdata.com/api/v1';
|
|
2
2
|
const API_KEY = process.env.GEOTAP_API_KEY || '';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Structured API Error with fix instructions for LLMs.
|
|
6
|
+
*/
|
|
7
|
+
export class StructuredApiError extends Error {
|
|
8
|
+
constructor(details) {
|
|
9
|
+
super(details.message);
|
|
10
|
+
this.name = 'StructuredApiError';
|
|
11
|
+
this.details = details;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Build a structured error with fix instructions and related tools.
|
|
17
|
+
*/
|
|
18
|
+
function buildStructuredError(status, errorText, endpoint, method, params) {
|
|
19
|
+
const base = {
|
|
20
|
+
error: true,
|
|
21
|
+
status,
|
|
22
|
+
message: `GeoTap API error (${status}): ${errorText}`,
|
|
23
|
+
endpoint,
|
|
24
|
+
method
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Parse common error patterns and provide actionable fixes
|
|
28
|
+
const fixes = [];
|
|
29
|
+
const relatedTools = [];
|
|
30
|
+
|
|
31
|
+
if (status === 400) {
|
|
32
|
+
if (/geometry|polygon|geojson/i.test(errorText)) {
|
|
33
|
+
fixes.push('Provide a valid GeoJSON geometry object, or use lat/lng parameters instead (they will be auto-converted to GeoJSON).');
|
|
34
|
+
fixes.push('Example: { "lat": 32.08, "lng": -81.09 } instead of a GeoJSON polygon.');
|
|
35
|
+
}
|
|
36
|
+
if (/missing|required/i.test(errorText)) {
|
|
37
|
+
const missingParam = errorText.match(/(?:missing|required)[:\s]+(\w+)/i)?.[1];
|
|
38
|
+
if (missingParam) {
|
|
39
|
+
fixes.push(`Add the required parameter: "${missingParam}".`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (/lat|lng|lon|coordinate/i.test(errorText)) {
|
|
43
|
+
fixes.push('Ensure lat is between -90 and 90, lng is between -180 and 180. Note: coordinates must be within the United States.');
|
|
44
|
+
relatedTools.push('geocode_address');
|
|
45
|
+
}
|
|
46
|
+
if (/layer/i.test(errorText)) {
|
|
47
|
+
fixes.push('Use list_data_layers to see valid layer names. Layer names use underscores (e.g., flood_zones, wetlands).');
|
|
48
|
+
relatedTools.push('list_data_layers');
|
|
49
|
+
}
|
|
50
|
+
if (/bbox|bounding/i.test(errorText)) {
|
|
51
|
+
fixes.push('Bounding box format: "west,south,east,north" in WGS84 (e.g., "-81.1,32.0,-81.0,32.1"). West must be less than east, south less than north.');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (status === 401 || status === 403) {
|
|
56
|
+
fixes.push('Set GEOTAP_API_KEY environment variable with a valid API key. Get one at https://geotapdata.com');
|
|
57
|
+
relatedTools.push('check_api_status');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (status === 404) {
|
|
61
|
+
if (/gage|site|station/i.test(endpoint)) {
|
|
62
|
+
fixes.push('Station/gage not found. Verify the site ID is a valid USGS station number (e.g., "08158000").');
|
|
63
|
+
relatedTools.push('find_monitoring_stations', 'search_stations');
|
|
64
|
+
}
|
|
65
|
+
if (/job/i.test(endpoint)) {
|
|
66
|
+
fixes.push('Job ID not found or expired. Submit a new request to start a fresh job.');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (status === 429) {
|
|
71
|
+
fixes.push('Rate limit exceeded. Wait a moment and retry, or upgrade your API key tier at https://geotapdata.com');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (status >= 500) {
|
|
75
|
+
fixes.push('Server error — the upstream federal data source may be temporarily unavailable.');
|
|
76
|
+
fixes.push('Try again in a few seconds, or check check_api_status to see which services are up.');
|
|
77
|
+
relatedTools.push('check_api_status');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (fixes.length === 0) {
|
|
81
|
+
fixes.push('Check that all required parameters are provided and valid.');
|
|
82
|
+
fixes.push('Use discover_tools to find the right tool for your question.');
|
|
83
|
+
relatedTools.push('discover_tools');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
...base,
|
|
88
|
+
fix: fixes,
|
|
89
|
+
relatedTools: [...new Set(relatedTools)]
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
4
93
|
/**
|
|
5
94
|
* Call the GeoTap API.
|
|
6
95
|
* Handles both GET (query params) and POST (JSON body) requests.
|
|
@@ -52,7 +141,8 @@ export async function callApi(endpoint, method, params) {
|
|
|
52
141
|
|
|
53
142
|
if (!response.ok) {
|
|
54
143
|
const errorText = await response.text().catch(() => 'Unknown error');
|
|
55
|
-
|
|
144
|
+
const structured = buildStructuredError(response.status, errorText, endpoint, method, params);
|
|
145
|
+
throw new StructuredApiError(structured);
|
|
56
146
|
}
|
|
57
147
|
|
|
58
148
|
// Check content type to handle binary vs JSON responses
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Consolidated Tool Definitions — 12 smart tools replacing 85 legacy tools
|
|
5
|
+
*
|
|
6
|
+
* Each tool uses an `action` parameter to route to the correct backend endpoint.
|
|
7
|
+
* This dramatically improves LLM tool selection accuracy (research shows accuracy
|
|
8
|
+
* drops from 95% at 5 tools to <14% at 40+ tools).
|
|
9
|
+
*
|
|
10
|
+
* Legacy tools remain available via GEOTAP_LEGACY_TOOLS=true env var.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const consolidatedTools = [
|
|
14
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
15
|
+
// 1. QUERY LOCATION — The starting point for most queries
|
|
16
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
17
|
+
{
|
|
18
|
+
name: 'query_location',
|
|
19
|
+
description: `Query environmental data for any US location. This is the primary tool — start here for most questions.
|
|
20
|
+
|
|
21
|
+
Actions:
|
|
22
|
+
- "address" — Geocode a US address AND query environmental data in one call. Always start here when user gives an address. Returns flood zones, wetlands, soils, habitat, contamination, with plain-English interpretations. Response <5KB.
|
|
23
|
+
- "point" — Same as address but when you already have lat/lng coordinates. Response <5KB.
|
|
24
|
+
- "nearby" — Find environmental features within a radius of a point. Set geometry="none" and specify layers to keep response small.
|
|
25
|
+
- "bbox" — Query features in a bounding box. Set geometry="none" and specify layers.
|
|
26
|
+
- "polygon" — Query features in a polygon area. Set geometry="none" and specify layers.
|
|
27
|
+
- "summary" — Quick feature counts for an area (no geometry, just numbers). Fastest option for "how many?" questions.
|
|
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.
|
|
30
|
+
- "layer_details" — Get metadata about a specific layer.
|
|
31
|
+
- "layer_features" — Get features from one specific layer in a bbox.`,
|
|
32
|
+
parameters: {
|
|
33
|
+
action: z.enum(['address', 'point', 'nearby', 'bbox', 'polygon', 'summary', 'geocode', 'list_layers', 'layer_details', 'layer_features'])
|
|
34
|
+
.describe('Which query type to perform'),
|
|
35
|
+
// Address actions
|
|
36
|
+
address: z.string().optional().describe('US street address (for action: address, geocode)'),
|
|
37
|
+
// Coordinate actions
|
|
38
|
+
lat: z.number().optional().describe('Latitude WGS84 (for action: point, nearby)'),
|
|
39
|
+
lng: z.number().optional().describe('Longitude WGS84 (for action: point, nearby)'),
|
|
40
|
+
// Area actions
|
|
41
|
+
bbox: z.string().optional().describe('Bounding box "west,south,east,north" (for action: bbox, layer_features)'),
|
|
42
|
+
polygon: z.object({
|
|
43
|
+
type: z.literal('Polygon'),
|
|
44
|
+
coordinates: z.array(z.array(z.array(z.number())))
|
|
45
|
+
}).optional().describe('GeoJSON Polygon (for action: polygon, summary)'),
|
|
46
|
+
// Filtering
|
|
47
|
+
layers: z.string().optional().describe('Comma-separated layer names (e.g., "flood_zones,wetlands")'),
|
|
48
|
+
geometry: z.enum(['none', 'simplified', 'full']).optional().describe('Geometry detail level. Default: none for MCP. Use "none" unless user specifically needs coordinates.'),
|
|
49
|
+
radius: z.number().optional().describe('Search radius in km for action: nearby (default: 1)'),
|
|
50
|
+
layerName: z.string().optional().describe('Layer identifier for action: layer_details, layer_features'),
|
|
51
|
+
},
|
|
52
|
+
_actionMap: {
|
|
53
|
+
address: 'query_address',
|
|
54
|
+
point: 'identify_features_at_point',
|
|
55
|
+
nearby: 'get_environmental_data_near_point',
|
|
56
|
+
bbox: 'get_environmental_data_in_bbox',
|
|
57
|
+
polygon: 'get_environmental_data_for_area',
|
|
58
|
+
summary: 'get_environmental_summary',
|
|
59
|
+
geocode: 'geocode_address',
|
|
60
|
+
list_layers: 'list_data_layers',
|
|
61
|
+
layer_details: 'get_layer_details',
|
|
62
|
+
layer_features: 'get_layer_features',
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
67
|
+
// 2. RAINFALL — NOAA Atlas 14, design storms, climate projections
|
|
68
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
69
|
+
{
|
|
70
|
+
name: 'get_rainfall',
|
|
71
|
+
description: `Get rainfall and precipitation data from NOAA Atlas 14 for stormwater engineering, flood analysis, and hydrologic design.
|
|
72
|
+
|
|
73
|
+
Actions:
|
|
74
|
+
- "atlas14" — Precipitation frequency estimates (depths & intensities for all durations/return periods). The standard rainfall data for US engineering.
|
|
75
|
+
- "idf" — Intensity-Duration-Frequency curve data for charting.
|
|
76
|
+
- "hyetograph" — Generate a design storm hyetograph (rainfall over time) for hydrologic modeling.
|
|
77
|
+
- "export_hyetograph" — Export hyetograph as CSV/JSON for HEC-HMS, SWMM, etc.
|
|
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.
|
|
80
|
+
- "climate_scenarios" — List available SSP scenarios and time horizons.
|
|
81
|
+
- "climate_factors" — Get climate change adjustment multipliers for a location.
|
|
82
|
+
- "climate_projection" — Apply climate change projections to Atlas 14 data.
|
|
83
|
+
- "uncertainty" — Get confidence interval bounds for a specific return period/duration.
|
|
84
|
+
- "uncertainty_envelope" — Monte Carlo uncertainty envelope for risk-based design.
|
|
85
|
+
- "sensitivity" — Sensitivity analysis on storm parameters.
|
|
86
|
+
- "design_approaches" — List design approaches for handling uncertainty.
|
|
87
|
+
- "status" — Check NOAA Atlas 14 service availability.`,
|
|
88
|
+
parameters: {
|
|
89
|
+
action: z.enum(['atlas14', 'idf', 'hyetograph', 'export_hyetograph', 'distributions', 'recommend_distribution', 'climate_scenarios', 'climate_factors', 'climate_projection', 'uncertainty', 'uncertainty_envelope', 'sensitivity', 'design_approaches', 'status'])
|
|
90
|
+
.describe('Which rainfall query to perform'),
|
|
91
|
+
lat: z.number().optional().describe('Latitude WGS84'),
|
|
92
|
+
lng: z.number().optional().describe('Longitude WGS84'),
|
|
93
|
+
units: z.enum(['english', 'metric']).optional().describe('Unit system (default: english)'),
|
|
94
|
+
series: z.enum(['pds', 'ams']).optional().describe('Statistical series (default: pds)'),
|
|
95
|
+
returnPeriod: z.string().optional().describe('Return period with "yr" suffix (e.g., "100yr")'),
|
|
96
|
+
returnPeriods: z.string().optional().describe('Comma-separated return periods (e.g., "2,5,10,25,50,100") for IDF'),
|
|
97
|
+
duration: z.number().optional().describe('Storm duration in hours'),
|
|
98
|
+
timeInterval: z.number().optional().describe('Time step in minutes'),
|
|
99
|
+
distribution: z.string().optional().describe('Rainfall distribution type (e.g., "SCS Type II")'),
|
|
100
|
+
horizon: z.string().optional().describe('Climate time horizon: "current", "mid-century", "late-century"'),
|
|
101
|
+
scenario: z.string().optional().describe('Climate scenario: "SSP2-4.5" or "SSP5-8.5"'),
|
|
102
|
+
format: z.enum(['csv', 'json']).optional().describe('Export format for export_hyetograph'),
|
|
103
|
+
nSamples: z.number().optional().describe('Monte Carlo samples for uncertainty_envelope (default: 500)'),
|
|
104
|
+
},
|
|
105
|
+
_actionMap: {
|
|
106
|
+
atlas14: 'get_rainfall_data',
|
|
107
|
+
idf: 'get_idf_curves',
|
|
108
|
+
hyetograph: 'generate_hyetograph',
|
|
109
|
+
export_hyetograph: 'export_hyetograph',
|
|
110
|
+
distributions: 'list_rainfall_distributions',
|
|
111
|
+
recommend_distribution: 'get_rainfall_distribution',
|
|
112
|
+
climate_scenarios: 'get_climate_scenarios',
|
|
113
|
+
climate_factors: 'get_climate_change_factors',
|
|
114
|
+
climate_projection: 'get_climate_change_rainfall_projection',
|
|
115
|
+
uncertainty: 'get_rainfall_uncertainty_bounds',
|
|
116
|
+
uncertainty_envelope: 'generate_uncertainty_envelope',
|
|
117
|
+
sensitivity: 'run_rainfall_sensitivity_analysis',
|
|
118
|
+
design_approaches: 'get_design_approaches',
|
|
119
|
+
status: 'check_rainfall_service_status',
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
124
|
+
// 3. WATERSHED — Delineation, flow statistics, HUC boundaries
|
|
125
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
126
|
+
{
|
|
127
|
+
name: 'get_watershed',
|
|
128
|
+
description: `Watershed delineation, flow statistics, stream networks, water quality, HUC boundaries, and FEMA flood map panels.
|
|
129
|
+
|
|
130
|
+
Actions:
|
|
131
|
+
- "delineate" — Trace watershed boundary for a pour point using USGS StreamStats. Returns drainage area polygon + basin characteristics.
|
|
132
|
+
- "characteristics" — Get physical/hydrologic basin characteristics (area, slope, precip, impervious %).
|
|
133
|
+
- "flow_statistics" — Estimated peak flows (2yr-500yr) and low flows from USGS regional regression.
|
|
134
|
+
- "flowlines" — Stream network (rivers, creeks) in a bounding box from NHD.
|
|
135
|
+
- "water_quality" — Water quality impairments (303d listed) for a watershed extent.
|
|
136
|
+
- "huc_boundaries" — HUC-8/10/12 watershed boundaries for a bounding box.
|
|
137
|
+
- "huc_by_code" — Get a specific HUC watershed boundary by its code.
|
|
138
|
+
- "firm_panels" — FEMA FIRM panel numbers for an area.`,
|
|
139
|
+
parameters: {
|
|
140
|
+
action: z.enum(['delineate', 'characteristics', 'flow_statistics', 'flowlines', 'water_quality', 'huc_boundaries', 'huc_by_code', 'firm_panels'])
|
|
141
|
+
.describe('Which watershed query to perform'),
|
|
142
|
+
lat: z.number().optional().describe('Latitude WGS84 (for delineate, characteristics, flow_statistics)'),
|
|
143
|
+
lng: z.number().optional().describe('Longitude WGS84'),
|
|
144
|
+
bbox: z.string().optional().describe('Bounding box "west,south,east,north" (for flowlines, water_quality, huc_boundaries, firm_panels)'),
|
|
145
|
+
region: z.string().optional().describe('StreamStats region code (auto-detected if omitted)'),
|
|
146
|
+
drainageArea: z.number().optional().describe('Known drainage area in sq mi (improves flow_statistics accuracy)'),
|
|
147
|
+
huc12: z.string().optional().describe('HUC-12 code (for water_quality if known)'),
|
|
148
|
+
hucCode: z.string().optional().describe('HUC code for huc_by_code'),
|
|
149
|
+
hucLevel: z.enum(['8', '10', '12']).optional().describe('HUC level for huc_boundaries (default: 12)'),
|
|
150
|
+
},
|
|
151
|
+
_actionMap: {
|
|
152
|
+
delineate: 'delineate_watershed',
|
|
153
|
+
characteristics: 'get_watershed_characteristics',
|
|
154
|
+
flow_statistics: 'get_flow_statistics',
|
|
155
|
+
flowlines: 'get_flowlines',
|
|
156
|
+
water_quality: 'get_watershed_water_quality',
|
|
157
|
+
huc_boundaries: 'get_huc_watersheds',
|
|
158
|
+
huc_by_code: 'get_huc_watershed_by_code',
|
|
159
|
+
firm_panels: 'get_firm_panels',
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
164
|
+
// 4. HYDROLOGY — Curve numbers, runoff, engineering calculations
|
|
165
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
166
|
+
{
|
|
167
|
+
name: 'get_hydrology',
|
|
168
|
+
description: `Hydrologic engineering calculations: curve numbers, runoff, time of concentration, peak discharge.
|
|
169
|
+
|
|
170
|
+
Actions:
|
|
171
|
+
- "analyze" — Comprehensive hydrologic analysis on catchments: composite CN, Tc, SCS runoff depth, peak discharge (Rational, TR-55, regression). The all-in-one hydrology tool.
|
|
172
|
+
- "distributions" — List rainfall distribution types for hydrologic analysis.
|
|
173
|
+
- "distribution_for_location" — Recommended SCS distribution for a specific location.
|
|
174
|
+
- "lookup_cn" — Look up SCS curve number for a specific land use (NLCD code) + soil type (HSG).
|
|
175
|
+
- "cn_tables" — Full SCS curve number reference tables.
|
|
176
|
+
- "analyze_cn" — Calculate weighted curve numbers for catchments using NLCD + SSURGO data.`,
|
|
177
|
+
parameters: {
|
|
178
|
+
action: z.enum(['analyze', 'distributions', 'distribution_for_location', 'lookup_cn', 'cn_tables', 'analyze_cn'])
|
|
179
|
+
.describe('Which hydrology calculation to perform'),
|
|
180
|
+
lat: z.number().optional().describe('Latitude WGS84 (for distribution_for_location)'),
|
|
181
|
+
lng: z.number().optional().describe('Longitude WGS84'),
|
|
182
|
+
catchments: z.any().optional().describe('GeoJSON FeatureCollection of catchment polygons (for analyze, analyze_cn)'),
|
|
183
|
+
options: z.any().optional().describe('Analysis options: {hydrologicCondition, dualHSGTreatment, returnPeriods, durations}'),
|
|
184
|
+
nlcd: z.number().optional().describe('NLCD land cover code for lookup_cn (e.g., 21=Developed Open Space)'),
|
|
185
|
+
hsg: z.string().optional().describe('Hydrologic Soil Group A/B/C/D for lookup_cn'),
|
|
186
|
+
condition: z.string().optional().describe('Antecedent moisture: "good", "fair", "poor" for lookup_cn'),
|
|
187
|
+
},
|
|
188
|
+
_actionMap: {
|
|
189
|
+
analyze: 'analyze_hydrology',
|
|
190
|
+
distributions: 'get_hydrology_distributions',
|
|
191
|
+
distribution_for_location: 'get_hydrology_distribution_for_location',
|
|
192
|
+
lookup_cn: 'lookup_curve_number',
|
|
193
|
+
cn_tables: 'get_curve_number_tables',
|
|
194
|
+
analyze_cn: 'analyze_curve_numbers',
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
199
|
+
// 5. WATER QUALITY — EPA ATTAINS, impairments, receiving waters
|
|
200
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
201
|
+
{
|
|
202
|
+
name: 'get_water_quality',
|
|
203
|
+
description: `Water quality impairment data from EPA ATTAINS — 303(d) listed impaired waters, pollutants, designated uses.
|
|
204
|
+
|
|
205
|
+
Actions:
|
|
206
|
+
- "assessment" — Full water quality assessment for a location: impaired waters, pollutants, downstream receiving water trace.
|
|
207
|
+
- "impairments" — Quick impairment check by HUC-12 code. Faster when you know the HUC.
|
|
208
|
+
- "find_watershed" — Identify which HUC-12 watershed a point falls within.`,
|
|
209
|
+
parameters: {
|
|
210
|
+
action: z.enum(['assessment', 'impairments', 'find_watershed'])
|
|
211
|
+
.describe('Which water quality query to perform'),
|
|
212
|
+
lat: z.number().optional().describe('Latitude WGS84 (for find_watershed)'),
|
|
213
|
+
lng: z.number().optional().describe('Longitude WGS84'),
|
|
214
|
+
location: z.any().optional().describe('GeoJSON Point or Polygon (for assessment)'),
|
|
215
|
+
huc12: z.string().optional().describe('12-digit HUC code (for impairments)'),
|
|
216
|
+
options: z.object({
|
|
217
|
+
includeDownstream: z.boolean().optional(),
|
|
218
|
+
radiusKm: z.number().optional(),
|
|
219
|
+
}).optional().describe('Assessment options'),
|
|
220
|
+
},
|
|
221
|
+
_actionMap: {
|
|
222
|
+
assessment: 'get_water_quality',
|
|
223
|
+
impairments: 'get_water_impairments',
|
|
224
|
+
find_watershed: 'get_watershed_for_point',
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
229
|
+
// 6. ELEVATION — USGS 3DEP, contours, DEM, land use, imagery
|
|
230
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
231
|
+
{
|
|
232
|
+
name: 'get_elevation',
|
|
233
|
+
description: `Elevation, terrain, land use, and satellite imagery from USGS 3DEP, NLCD, and NAIP.
|
|
234
|
+
|
|
235
|
+
Actions:
|
|
236
|
+
- "stats" — Elevation statistics (min, max, mean, range) for a bounding box.
|
|
237
|
+
- "contours" — Generate contour lines at specified intervals for a bbox.
|
|
238
|
+
- "contour_options" — Available contour interval options.
|
|
239
|
+
- "export_dem" — Export DEM as GeoTIFF (1m/10m/30m resolution).
|
|
240
|
+
- "export_contours" — Export contour lines as GeoJSON for a polygon.
|
|
241
|
+
- "availability" — Check which DEM resolutions are available for an area.
|
|
242
|
+
- "resolution_options" — List supported DEM resolution options.
|
|
243
|
+
- "export_land_use" — Export NLCD land cover data as GeoTIFF or polygons.
|
|
244
|
+
- "export_satellite" — Export aerial photography as GeoTIFF.
|
|
245
|
+
- "satellite_options" — Available satellite imagery resolutions.`,
|
|
246
|
+
parameters: {
|
|
247
|
+
action: z.enum(['stats', 'contours', 'contour_options', 'export_dem', 'export_contours', 'availability', 'resolution_options', 'export_land_use', 'export_satellite', 'satellite_options'])
|
|
248
|
+
.describe('Which elevation/terrain query to perform'),
|
|
249
|
+
bbox: z.string().optional().describe('Bounding box "west,south,east,north" (for stats, contours, availability)'),
|
|
250
|
+
polygon: z.any().optional().describe('GeoJSON Polygon (for export_dem, export_contours, export_land_use, export_satellite)'),
|
|
251
|
+
interval: z.number().optional().describe('Contour interval in feet'),
|
|
252
|
+
intervalMeters: z.number().optional().describe('Contour interval in meters'),
|
|
253
|
+
resolution: z.string().optional().describe('DEM resolution "1m"/"10m"/"30m" or satellite "high"/"medium"/"low"'),
|
|
254
|
+
targetCrs: z.string().optional().describe('Target CRS (e.g., "EPSG:2277")'),
|
|
255
|
+
convertToFeet: z.boolean().optional().describe('Convert elevations to feet'),
|
|
256
|
+
clipToPolygon: z.boolean().optional().describe('Clip raster to polygon boundary'),
|
|
257
|
+
format: z.enum(['geotiff', 'polygons']).optional().describe('Land use export format'),
|
|
258
|
+
demResolution: z.enum(['1m', '10m', '30m']).optional().describe('DEM resolution for contour export'),
|
|
259
|
+
},
|
|
260
|
+
_actionMap: {
|
|
261
|
+
stats: 'get_elevation_stats',
|
|
262
|
+
contours: 'get_contour_lines',
|
|
263
|
+
contour_options: 'get_contour_interval_options',
|
|
264
|
+
export_dem: 'export_dem',
|
|
265
|
+
export_contours: 'export_contours',
|
|
266
|
+
availability: 'check_dem_availability',
|
|
267
|
+
resolution_options: 'get_dem_resolution_options',
|
|
268
|
+
export_land_use: 'export_land_use',
|
|
269
|
+
export_satellite: 'export_satellite_imagery',
|
|
270
|
+
satellite_options: 'get_satellite_resolution_options',
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
275
|
+
// 7. GAGE ANALYSIS — Stream gage data, flood frequency, storm events
|
|
276
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
277
|
+
{
|
|
278
|
+
name: 'analyze_gage',
|
|
279
|
+
description: `Analyze USGS stream gage data: flood frequency, flow duration, low flow, storm events, and published statistics.
|
|
280
|
+
|
|
281
|
+
Actions:
|
|
282
|
+
- "summary" — Quick overview of a gage: period of record, drainage area, key flows.
|
|
283
|
+
- "flood_frequency" — Bulletin 17C flood frequency analysis (2yr-500yr peak flows).
|
|
284
|
+
- "flow_duration" — Flow duration curve and percentiles (Q1 through Q99).
|
|
285
|
+
- "low_flow" — Low flow statistics: 7Q10, 7Q2, harmonic mean. Critical for NPDES permits.
|
|
286
|
+
- "storm_events" — Detect storm events from flow record with peak, volume, duration.
|
|
287
|
+
- "storm_detail" — Detailed hydrograph for a specific storm event.
|
|
288
|
+
- "export_storm" — Export storm hydrograph for HEC-HMS or other models.
|
|
289
|
+
- "published_stats" — Official USGS GageStats published values (peer-reviewed).
|
|
290
|
+
- "compare_stats" — Compare computed vs. published statistics for QA.`,
|
|
291
|
+
parameters: {
|
|
292
|
+
action: z.enum(['summary', 'flood_frequency', 'flow_duration', 'low_flow', 'storm_events', 'storm_detail', 'export_storm', 'published_stats', 'compare_stats'])
|
|
293
|
+
.describe('Which gage analysis to perform'),
|
|
294
|
+
siteId: z.string().describe('USGS station ID (e.g., "08158000")'),
|
|
295
|
+
eventId: z.string().optional().describe('Storm event ID (for storm_detail, export_storm)'),
|
|
296
|
+
minYears: z.number().optional().describe('Minimum years of record required'),
|
|
297
|
+
startDate: z.string().optional().describe('Start date YYYY-MM-DD (for flow_duration)'),
|
|
298
|
+
endDate: z.string().optional().describe('End date YYYY-MM-DD (for flow_duration)'),
|
|
299
|
+
period: z.string().optional().describe('Time period "1y"/"5y"/"10y" (for storm_events)'),
|
|
300
|
+
minPeak: z.number().optional().describe('Minimum peak flow in cfs (for storm_events)'),
|
|
301
|
+
format: z.string().optional().describe('Export format (default: "hec-hms")'),
|
|
302
|
+
},
|
|
303
|
+
_actionMap: {
|
|
304
|
+
summary: 'get_gage_summary',
|
|
305
|
+
flood_frequency: 'get_flood_frequency_analysis',
|
|
306
|
+
flow_duration: 'get_flow_duration_curve',
|
|
307
|
+
low_flow: 'get_low_flow_statistics',
|
|
308
|
+
storm_events: 'get_storm_events',
|
|
309
|
+
storm_detail: 'get_storm_event_detail',
|
|
310
|
+
export_storm: 'export_storm_event_for_modeling',
|
|
311
|
+
published_stats: 'get_published_gage_statistics',
|
|
312
|
+
compare_stats: 'compare_computed_vs_published_stats',
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
317
|
+
// 8. UNGAGED ESTIMATION — Flow estimates at sites without gages
|
|
318
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
319
|
+
{
|
|
320
|
+
name: 'estimate_ungaged',
|
|
321
|
+
description: `Estimate flows at ungaged sites using USGS regional regression, watershed similarity, and drainage area transfer methods.
|
|
322
|
+
|
|
323
|
+
Actions:
|
|
324
|
+
- "flood_frequency" — Estimate flood flows using NSS regional regression equations. Requires state, region, and basin characteristics.
|
|
325
|
+
- "all_statistics" — Estimate ALL available flow statistics (peak, low, duration) for an ungaged site.
|
|
326
|
+
- "nss_regions" — List available NSS regions for a state.
|
|
327
|
+
- "required_parameters" — What basin characteristics are needed for a state/region.
|
|
328
|
+
- "find_similar" — Find gauged watersheds with similar physical characteristics.
|
|
329
|
+
- "find_similar_with_stats" — Similar watersheds + their published flow statistics.
|
|
330
|
+
- "recommend_index" — Find the best reference gage for flow transfer.
|
|
331
|
+
- "transfer_stats" — Transfer flood statistics from a reference gage using drainage area ratio.`,
|
|
332
|
+
parameters: {
|
|
333
|
+
action: z.enum(['flood_frequency', 'all_statistics', 'nss_regions', 'required_parameters', 'find_similar', 'find_similar_with_stats', 'recommend_index', 'transfer_stats'])
|
|
334
|
+
.describe('Which estimation method to use'),
|
|
335
|
+
lat: z.number().optional().describe('Latitude of ungaged site'),
|
|
336
|
+
lng: z.number().optional().describe('Longitude of ungaged site'),
|
|
337
|
+
state: z.string().optional().describe('US state code (e.g., "TX")'),
|
|
338
|
+
region: z.string().optional().describe('NSS region code'),
|
|
339
|
+
parameters: z.any().optional().describe('Basin characteristics object (e.g., {drainageArea: 10.5, meanBasinSlope: 3.2})'),
|
|
340
|
+
characteristics: z.any().optional().describe('Known basin characteristics for similarity matching'),
|
|
341
|
+
maxDistance: z.number().optional().describe('Max search distance in km for similarity'),
|
|
342
|
+
limit: z.number().optional().describe('Max results'),
|
|
343
|
+
indexSiteId: z.string().optional().describe('Reference gage ID for transfer_stats'),
|
|
344
|
+
targetDrainageArea: z.number().optional().describe('Ungaged site drainage area in sq mi for transfer_stats'),
|
|
345
|
+
drainageArea: z.number().optional().describe('Drainage area for recommend_index'),
|
|
346
|
+
},
|
|
347
|
+
_actionMap: {
|
|
348
|
+
flood_frequency: 'estimate_ungaged_flood_frequency',
|
|
349
|
+
all_statistics: 'estimate_all_ungaged_statistics',
|
|
350
|
+
nss_regions: 'get_ungaged_nss_regions',
|
|
351
|
+
required_parameters: 'get_ungaged_required_parameters',
|
|
352
|
+
find_similar: 'find_similar_watersheds',
|
|
353
|
+
find_similar_with_stats: 'find_similar_watersheds_with_stats',
|
|
354
|
+
recommend_index: 'recommend_index_gage',
|
|
355
|
+
transfer_stats: 'transfer_flood_statistics',
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
|
|
359
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
360
|
+
// 9. GENERATE REPORT — Site analysis, constraints, developability
|
|
361
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
362
|
+
{
|
|
363
|
+
name: 'generate_report',
|
|
364
|
+
description: `Generate comprehensive environmental reports for development sites.
|
|
365
|
+
|
|
366
|
+
Actions:
|
|
367
|
+
- "site_analysis" — Full environmental site analysis: flood risk, wetlands, soils, hazards, habitat, contamination. Returns developability score 0-100.
|
|
368
|
+
- "site_analysis_status" — Check status of a site analysis job (can take 30-60s).
|
|
369
|
+
- "constraints" — Environmental constraints report: floodway, flood zones, wetlands, hydric soils, steep slopes. Calculates constrained vs. developable area.
|
|
370
|
+
- "constraints_status" — Check status of constraints report job.
|
|
371
|
+
- "constraints_config" — Available constraint report options.
|
|
372
|
+
- "developability" — Site developability assessment with 0-100 score and penalty breakdown.
|
|
373
|
+
- "developability_config" — Available developability assessment options.`,
|
|
374
|
+
parameters: {
|
|
375
|
+
action: z.enum(['site_analysis', 'site_analysis_status', 'constraints', 'constraints_status', 'constraints_config', 'developability', 'developability_config'])
|
|
376
|
+
.describe('Which report to generate or check'),
|
|
377
|
+
geometry: z.any().optional().describe('GeoJSON Point or Polygon for the site'),
|
|
378
|
+
projectName: z.string().optional().describe('Project name for report'),
|
|
379
|
+
clientName: z.string().optional().describe('Client name for report'),
|
|
380
|
+
jobId: z.string().optional().describe('Job ID for status checks'),
|
|
381
|
+
options: z.any().optional().describe('Report options'),
|
|
382
|
+
},
|
|
383
|
+
_actionMap: {
|
|
384
|
+
site_analysis: 'generate_site_analysis',
|
|
385
|
+
site_analysis_status: 'get_site_analysis_status',
|
|
386
|
+
constraints: 'generate_constraints_report',
|
|
387
|
+
constraints_status: 'get_constraints_report_status',
|
|
388
|
+
constraints_config: 'get_constraints_config',
|
|
389
|
+
developability: 'generate_developability_report',
|
|
390
|
+
developability_config: 'get_developability_config',
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
|
|
394
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
395
|
+
// 10. EXPORT DATA — Multi-format GIS export
|
|
396
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
397
|
+
{
|
|
398
|
+
name: 'export_data',
|
|
399
|
+
description: `Export environmental data layers to GIS formats (GeoJSON, Shapefile, KML, CSV, GeoPackage).
|
|
400
|
+
|
|
401
|
+
Actions:
|
|
402
|
+
- "export" — Export layers to a file format. Supports CRS transformation and clipping.
|
|
403
|
+
- "options" — List available export formats and CRS options.
|
|
404
|
+
- "status" — Check export job status.`,
|
|
405
|
+
parameters: {
|
|
406
|
+
action: z.enum(['export', 'options', 'status'])
|
|
407
|
+
.describe('Which export operation'),
|
|
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'),
|
|
410
|
+
crs: z.string().optional().describe('Target CRS (e.g., "EPSG:4326")'),
|
|
411
|
+
geometry: z.any().optional().describe('GeoJSON geometry to clip export area'),
|
|
412
|
+
options: z.any().optional().describe('Additional options: {dem, satellite, nlcd, contours}'),
|
|
413
|
+
jobId: z.string().optional().describe('Job ID for status check'),
|
|
414
|
+
},
|
|
415
|
+
_actionMap: {
|
|
416
|
+
export: 'export_data',
|
|
417
|
+
options: 'get_export_options',
|
|
418
|
+
status: 'get_export_status',
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
|
|
422
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
423
|
+
// 11. FIND STATIONS — Monitoring stations & permits
|
|
424
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
425
|
+
{
|
|
426
|
+
name: 'find_stations',
|
|
427
|
+
description: `Search for environmental monitoring stations (USGS streamgages, groundwater wells, weather stations, tide gauges) and analyze waterway permit requirements.
|
|
428
|
+
|
|
429
|
+
Actions:
|
|
430
|
+
- "search_area" — Find stations near a location or in a bounding box.
|
|
431
|
+
- "search_name" — Search stations by name or ID.
|
|
432
|
+
- "station_types" — List all available station types.
|
|
433
|
+
- "find_water_features" — Find streams, wetlands, waterbodies for permit analysis.
|
|
434
|
+
- "analyze_permits" — Determine required permits (Section 404, NPDES, etc.) for an activity near water.`,
|
|
435
|
+
parameters: {
|
|
436
|
+
action: z.enum(['search_area', 'search_name', 'station_types', 'find_water_features', 'analyze_permits'])
|
|
437
|
+
.describe('Which search to perform'),
|
|
438
|
+
// Station search params
|
|
439
|
+
bbox: z.string().optional().describe('Bounding box for area search'),
|
|
440
|
+
source: z.string().optional().describe('Data source filter: "usgs", "noaa"'),
|
|
441
|
+
type: z.string().optional().describe('Station type: "stream_gage", "groundwater", "tide", "precipitation"'),
|
|
442
|
+
state: z.string().optional().describe('US state code'),
|
|
443
|
+
q: z.string().optional().describe('Search query for search_name'),
|
|
444
|
+
limit: z.number().optional().describe('Max results'),
|
|
445
|
+
// Permit params
|
|
446
|
+
polygon: z.any().optional().describe('GeoJSON Polygon for find_water_features'),
|
|
447
|
+
selectedFeatures: z.any().optional().describe('Water features from find_water_features for analyze_permits'),
|
|
448
|
+
activityType: z.enum(['crossing', 'utility_crossing', 'stormwater_discharge', 'wetland_fill', 'bank_stabilization', 'adjacent_construction']).optional()
|
|
449
|
+
.describe('Activity type for analyze_permits'),
|
|
450
|
+
location: z.any().optional().describe('GeoJSON Point for activity location'),
|
|
451
|
+
},
|
|
452
|
+
_actionMap: {
|
|
453
|
+
search_area: 'find_monitoring_stations',
|
|
454
|
+
search_name: 'search_stations',
|
|
455
|
+
station_types: 'get_station_types',
|
|
456
|
+
find_water_features: 'find_water_features',
|
|
457
|
+
analyze_permits: 'analyze_permit_requirements',
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
|
|
461
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
462
|
+
// 12. CHECK STATUS — API health
|
|
463
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
464
|
+
{
|
|
465
|
+
name: 'check_status',
|
|
466
|
+
description: `Check GeoTap API health and federal data source connectivity.
|
|
467
|
+
|
|
468
|
+
Actions:
|
|
469
|
+
- "all" — Check all connected federal APIs (FEMA, USGS, EPA, NOAA, etc.).
|
|
470
|
+
- "specific" — Check one specific API.`,
|
|
471
|
+
parameters: {
|
|
472
|
+
action: z.enum(['all', 'specific']).describe('Check all APIs or a specific one'),
|
|
473
|
+
apiName: z.string().optional().describe('API name for specific check: "fema", "usgs", "epa", "noaa", "nrcs"'),
|
|
474
|
+
},
|
|
475
|
+
_actionMap: {
|
|
476
|
+
all: 'check_api_status',
|
|
477
|
+
specific: 'check_specific_api_status',
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
];
|