geotap-mcp-server 2.2.1 → 3.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/README.md +2 -2
- package/package.json +4 -3
- package/src/api.js +1 -1
- package/src/index.js +129 -275
- package/src/consolidatedTools.js +0 -602
- package/src/discoverTools.js +0 -211
- package/src/latLngHelper.js +0 -137
- package/src/paramNormalize.js +0 -182
- package/src/responseCap.js +0 -233
- package/src/sources.js +0 -199
- package/src/summaries.js +0 -396
- package/src/tools.js +0 -1222
- package/tests/Spec_Comprehensive_Test_Suite.md +0 -1203
package/README.md
CHANGED
|
@@ -449,7 +449,7 @@ curl -X POST "https://geotapdata.com/api/v1/export" \
|
|
|
449
449
|
|
|
450
450
|
## MCP Server
|
|
451
451
|
|
|
452
|
-
The MCP (Model Context Protocol) server wraps the REST API into **
|
|
452
|
+
The MCP (Model Context Protocol) server wraps the REST API into **16 consolidated tools** (or 109 legacy tools) that Claude, Cursor, Windsurf, and other AI assistants can call directly.
|
|
453
453
|
|
|
454
454
|
### Installation
|
|
455
455
|
|
|
@@ -529,7 +529,7 @@ With API key:
|
|
|
529
529
|
- *"What permits do I need to build near this stream?"*
|
|
530
530
|
- *"Export this data as a shapefile"*
|
|
531
531
|
|
|
532
|
-
### Available Tools (
|
|
532
|
+
### Available Tools (109 legacy / 16 consolidated)
|
|
533
533
|
|
|
534
534
|
**Core tools (start here):**
|
|
535
535
|
- **query_address** — Geocode + environmental query in one call. Returns properties with plain-English interpretations. (<5KB response)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "geotap-mcp-server",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "MCP server for GeoTap —
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "MCP server for GeoTap — collect comprehensive environmental data from 80+ US federal sources for any site. One tool, all the data.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -45,7 +45,8 @@
|
|
|
45
45
|
"author": "GeoTap",
|
|
46
46
|
"license": "MIT",
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@modelcontextprotocol/sdk": "^1.12.1"
|
|
48
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
49
|
+
"zod": "^4.3.6"
|
|
49
50
|
},
|
|
50
51
|
"engines": {
|
|
51
52
|
"node": ">=18.0.0"
|
package/src/api.js
CHANGED
|
@@ -102,7 +102,7 @@ function buildStructuredError(status, errorText, endpoint, method, params) {
|
|
|
102
102
|
export async function callApi(endpoint, method, params) {
|
|
103
103
|
const headers = {
|
|
104
104
|
'Content-Type': 'application/json',
|
|
105
|
-
'User-Agent': 'geotap-mcp-server/
|
|
105
|
+
'User-Agent': 'geotap-mcp-server/2.2.1'
|
|
106
106
|
};
|
|
107
107
|
|
|
108
108
|
if (API_KEY) {
|
package/src/index.js
CHANGED
|
@@ -3,267 +3,174 @@
|
|
|
3
3
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
4
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
5
|
import { z } from 'zod';
|
|
6
|
-
import { tools } from './tools.js';
|
|
7
|
-
import { consolidatedTools } from './consolidatedTools.js';
|
|
8
6
|
import { callApi, StructuredApiError } from './api.js';
|
|
9
|
-
import { toolSources } from './sources.js';
|
|
10
|
-
import { capResponse } from './responseCap.js';
|
|
11
|
-
import { generateSummary } from './summaries.js';
|
|
12
|
-
import { convertLatLng } from './latLngHelper.js';
|
|
13
|
-
import { normalizeParams } from './paramNormalize.js';
|
|
14
|
-
import { discoverTools } from './discoverTools.js';
|
|
15
7
|
import { readFileSync } from 'fs';
|
|
16
8
|
import { fileURLToPath } from 'url';
|
|
17
9
|
import { dirname, join } from 'path';
|
|
18
10
|
|
|
19
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
|
-
const useLegacyTools = process.env.GEOTAP_LEGACY_TOOLS === 'true';
|
|
21
|
-
|
|
22
|
-
// Build lookup map for legacy tools (used by both modes)
|
|
23
|
-
const legacyToolMap = new Map(tools.map(t => [t.name, t]));
|
|
24
12
|
|
|
25
13
|
const server = new McpServer({
|
|
26
14
|
name: 'geotap',
|
|
27
|
-
version: '
|
|
28
|
-
description: '
|
|
29
|
-
instructions:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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.
|
|
50
|
-
|
|
51
|
-
Every tool uses an "action" parameter to select the specific operation. Read the tool description to see available actions.
|
|
52
|
-
|
|
53
|
-
COORDINATE FLEXIBILITY:
|
|
54
|
-
- All tools accept lat/lng, lat/lon, or latitude/longitude — they are automatically normalized.
|
|
55
|
-
- POST tools accept flat lat/lng instead of GeoJSON — auto-converted to the correct format.
|
|
56
|
-
|
|
57
|
-
RESPONSE SIZE:
|
|
58
|
-
- geometry defaults to "none" for spatial queries (prevents context overflow).
|
|
59
|
-
- Responses auto-capped at 40KB (~10K tokens). Use specific layers to get smaller responses.
|
|
60
|
-
- Always specify layers (e.g., layers="flood_zones,wetlands") instead of querying all layers.
|
|
61
|
-
|
|
62
|
-
COMMON WORKFLOWS:
|
|
63
|
-
- "What flood zone is this address in?" → query_location(action: "address", address: "...")
|
|
64
|
-
- "What's the 100-year rainfall?" → get_rainfall(action: "atlas14", lat, lng)
|
|
65
|
-
- "Delineate the watershed" → get_watershed(action: "delineate", lat, lng)
|
|
66
|
-
- "Environmental site analysis" → generate_report(action: "site_analysis", geometry: ...)
|
|
67
|
-
- "What permits do I need?" → find_stations(action: "find_water_features") → find_stations(action: "analyze_permits")
|
|
15
|
+
version: '3.0.0',
|
|
16
|
+
description: 'Collect comprehensive environmental and infrastructure data from 80+ US federal sources for any site in the United States. Returns raw data from FEMA, USGS, NOAA, EPA, NRCS, USFWS, USACE, DOE, DOT, CDC, Census, and more.',
|
|
17
|
+
instructions: `You have access to GeoTap, which collects data from 80+ US federal agencies for any site in the United States.
|
|
18
|
+
|
|
19
|
+
HOW IT WORKS:
|
|
20
|
+
1. Call collect_site_data with a site location (address, coordinates, or GeoJSON geometry)
|
|
21
|
+
2. You get back a jobId — the backend queries all 80+ federal sources (takes 60-120 seconds)
|
|
22
|
+
3. Poll get_results every 10 seconds with the jobId until status is "completed"
|
|
23
|
+
4. When complete, you receive ALL available data for that site — present it to the user
|
|
24
|
+
|
|
25
|
+
DATA INCLUDES:
|
|
26
|
+
- Flood zones (FEMA NFHL), wetlands (NWI), soils (NRCS SSURGO), geology
|
|
27
|
+
- Contamination: Superfund, brownfields, USTs, EPA-regulated facilities (RCRA, GHG, FRS)
|
|
28
|
+
- Water: streams, watershed, water quality impairments (ATTAINS), NPDES outfalls, groundwater
|
|
29
|
+
- Hazards: seismic design (ASCE 7-22), earthquakes, wildfires, landslides, coastal vulnerability, NRI
|
|
30
|
+
- Rainfall: NOAA Atlas 14 precipitation frequency data
|
|
31
|
+
- Infrastructure: hospitals, fire stations, schools, EMS, dams, levees, power plants, airports, railroads, bridges
|
|
32
|
+
- Ecology: species (GBIF), fish habitat, critical habitat, cropland, national forests, BLM lands, historic places
|
|
33
|
+
- Energy: solar potential, utility rates, EV charging stations
|
|
34
|
+
- Demographics: Census ACS (population, income, housing, poverty)
|
|
35
|
+
- Protected lands (PAD-US), wild & scenic rivers, sole source aquifers
|
|
68
36
|
|
|
69
37
|
IMPORTANT:
|
|
70
38
|
- All data from authoritative US federal sources. Always cite the source agency.
|
|
71
|
-
- Responses include _summary with plain-English descriptions — use these in answers.
|
|
72
39
|
- Data is for informational purposes. Remind users to verify for engineering/regulatory decisions.
|
|
73
40
|
- Coordinates must be within the United States (including territories).
|
|
74
|
-
-
|
|
41
|
+
- If a data source returns "_noData: true", it was queried but found nothing at that location.`
|
|
75
42
|
});
|
|
76
43
|
|
|
77
|
-
// ──
|
|
44
|
+
// ── Tool: collect_site_data ──────────────────────────────────────────
|
|
78
45
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
*
|
|
83
|
-
* @param {string} legacyToolName - The original tool name (for routing summaries/sources)
|
|
84
|
-
* @param {string} endpoint - API endpoint path
|
|
85
|
-
* @param {string} method - HTTP method (GET/POST)
|
|
86
|
-
* @param {object} params - Parameters from the LLM (after action extraction)
|
|
87
|
-
* @returns {object} MCP tool response
|
|
88
|
-
*/
|
|
89
|
-
async function handleToolCall(legacyToolName, endpoint, method, params) {
|
|
90
|
-
try {
|
|
91
|
-
// Fix #1: Normalize coordinate params to what backend expects
|
|
92
|
-
const normalized = normalizeParams(legacyToolName, params);
|
|
46
|
+
server.tool(
|
|
47
|
+
'collect_site_data',
|
|
48
|
+
`Collect comprehensive environmental data from ALL 80+ federal data sources for a site. Accepts an address, lat/lng coordinates, or a GeoJSON geometry (Point or Polygon). Returns a jobId — poll with get_results until complete (60-120 seconds).
|
|
93
49
|
|
|
94
|
-
|
|
95
|
-
|
|
50
|
+
Data returned covers: flood zones, wetlands, soils, geology, contamination sites, water quality, seismic risk, rainfall, infrastructure, ecology, energy, demographics, and much more.`,
|
|
51
|
+
{
|
|
52
|
+
address: z.string().optional().describe('US street address (e.g., "123 Main St, Houston TX"). If provided, the site is geocoded automatically. Use this OR lat/lng OR geometry.'),
|
|
53
|
+
lat: z.number().optional().describe('Latitude of the site (e.g., 34.8441). Use with lng.'),
|
|
54
|
+
lng: z.number().optional().describe('Longitude of the site (e.g., -82.4010). Use with lat.'),
|
|
55
|
+
geometry: z.any().optional().describe('GeoJSON geometry (Point or Polygon). For advanced use — most users should use address or lat/lng instead.'),
|
|
56
|
+
bufferAcres: z.number().optional().describe('Site area in acres when using a point location. Creates a circular buffer. Default: 1 acre. Range: 0.1–640.'),
|
|
57
|
+
searchRadiusMiles: z.number().optional().describe('How far to search for nearby features (contamination, infrastructure, etc.). Default: 3 miles. Range: 0.5–10.'),
|
|
58
|
+
},
|
|
59
|
+
async (params) => {
|
|
60
|
+
try {
|
|
61
|
+
const { address, lat, lng, geometry, bufferAcres, searchRadiusMiles } = params;
|
|
96
62
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
delete apiParams._latLngConverted;
|
|
63
|
+
// Build the geometry from whatever input was provided
|
|
64
|
+
let siteGeometry = geometry;
|
|
100
65
|
|
|
101
|
-
|
|
66
|
+
if (address && !siteGeometry) {
|
|
67
|
+
// Geocode the address first
|
|
68
|
+
const geocodeResult = await callApi('/geocode', 'GET', { address });
|
|
69
|
+
if (!geocodeResult?.lat || !geocodeResult?.lng) {
|
|
70
|
+
return {
|
|
71
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
72
|
+
error: true,
|
|
73
|
+
message: `Could not geocode address: "${address}". Try a more specific address or use lat/lng coordinates.`,
|
|
74
|
+
}, null, 2) }],
|
|
75
|
+
isError: true
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
siteGeometry = {
|
|
79
|
+
type: 'Point',
|
|
80
|
+
coordinates: [geocodeResult.lng, geocodeResult.lat]
|
|
81
|
+
};
|
|
82
|
+
} else if (lat != null && lng != null && !siteGeometry) {
|
|
83
|
+
siteGeometry = {
|
|
84
|
+
type: 'Point',
|
|
85
|
+
coordinates: [lng, lat]
|
|
86
|
+
};
|
|
87
|
+
}
|
|
102
88
|
|
|
103
|
-
|
|
104
|
-
|
|
89
|
+
if (!siteGeometry) {
|
|
90
|
+
return {
|
|
91
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
92
|
+
error: true,
|
|
93
|
+
message: 'Provide a site location: address, lat/lng, or geometry.',
|
|
94
|
+
examples: [
|
|
95
|
+
{ address: '123 Main St, Houston TX' },
|
|
96
|
+
{ lat: 34.8441, lng: -82.4010 },
|
|
97
|
+
{ geometry: { type: 'Point', coordinates: [-82.4010, 34.8441] } }
|
|
98
|
+
]
|
|
99
|
+
}, null, 2) }],
|
|
100
|
+
isError: true
|
|
101
|
+
};
|
|
102
|
+
}
|
|
105
103
|
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
// Start the data collection job
|
|
105
|
+
const body = { geometry: siteGeometry };
|
|
106
|
+
if (bufferAcres != null) body.bufferAcres = bufferAcres;
|
|
107
|
+
if (searchRadiusMiles != null) body.searchRadiusMiles = searchRadiusMiles;
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
const dataQuality = checkDataQuality(legacyToolName, cappedResult);
|
|
109
|
+
const result = await callApi('/site-analysis/data-collect', 'POST', body);
|
|
111
110
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
111
|
+
return {
|
|
112
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
113
|
+
...result,
|
|
114
|
+
_instructions: 'Job started. Poll get_results with this jobId every 10 seconds until status is "completed". Data collection queries 80+ federal sources and takes 60-120 seconds.',
|
|
115
|
+
}, null, 2) }]
|
|
116
|
+
};
|
|
117
|
+
} catch (error) {
|
|
118
|
+
if (error instanceof StructuredApiError) {
|
|
119
|
+
return {
|
|
120
|
+
content: [{ type: 'text', text: JSON.stringify(error.details, null, 2) }],
|
|
121
|
+
isError: true
|
|
122
|
+
};
|
|
124
123
|
}
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
return {
|
|
128
|
-
content: [{ type: 'text', text: JSON.stringify(enriched, null, 2) }]
|
|
129
|
-
};
|
|
130
|
-
} catch (error) {
|
|
131
|
-
if (error instanceof StructuredApiError) {
|
|
132
124
|
return {
|
|
133
|
-
content: [{ type: 'text', text: JSON.stringify(error.
|
|
125
|
+
content: [{ type: 'text', text: JSON.stringify({ error: true, message: error.message }, null, 2) }],
|
|
134
126
|
isError: true
|
|
135
127
|
};
|
|
136
128
|
}
|
|
137
|
-
|
|
138
|
-
return {
|
|
139
|
-
content: [{ type: 'text', text: JSON.stringify({
|
|
140
|
-
error: true,
|
|
141
|
-
message: error.message,
|
|
142
|
-
fix: ['Check that all required parameters are provided and valid.', 'Use discover_tools or check the tool description for available actions.'],
|
|
143
|
-
relatedTools: ['discover_tools', 'check_status']
|
|
144
|
-
}, null, 2) }],
|
|
145
|
-
isError: true
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// ── Fix #5: Data quality warnings ───────────────────────────────────
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Check API response for signs of degraded or incomplete data.
|
|
154
|
-
* Returns a _dataQuality object with warnings, or null if no issues detected.
|
|
155
|
-
*/
|
|
156
|
-
function checkDataQuality(toolName, result) {
|
|
157
|
-
if (!result || typeof result !== 'object') return null;
|
|
158
|
-
|
|
159
|
-
const warnings = [];
|
|
160
|
-
|
|
161
|
-
// Check for empty layer results in spatial queries
|
|
162
|
-
if (result.layers && typeof result.layers === 'object') {
|
|
163
|
-
const emptyLayers = [];
|
|
164
|
-
const populatedLayers = [];
|
|
165
|
-
for (const [name, data] of Object.entries(result.layers)) {
|
|
166
|
-
if (data?.features?.length === 0 || data?.featureCount === 0) {
|
|
167
|
-
emptyLayers.push(name);
|
|
168
|
-
} else if (data?.features?.length > 0) {
|
|
169
|
-
populatedLayers.push(name);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
if (emptyLayers.length > 0 && populatedLayers.length > 0) {
|
|
173
|
-
// Only warn if some layers returned data but others didn't
|
|
174
|
-
// (if ALL are empty, that's a valid "nothing here" result)
|
|
175
|
-
warnings.push({
|
|
176
|
-
type: 'partial_results',
|
|
177
|
-
message: `${emptyLayers.length} layer(s) returned no features: ${emptyLayers.join(', ')}. This may be expected (no features at this location) or may indicate a data gap.`,
|
|
178
|
-
layers: emptyLayers,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Check for empty watershed geometry (the 82% failure pattern)
|
|
184
|
-
if (toolName === 'delineate_watershed' || toolName === 'get_watershed') {
|
|
185
|
-
const geom = result.data?.geometry || result.geometry || result.boundary?.geometry;
|
|
186
|
-
if (geom && geom.coordinates && geom.coordinates.length === 0) {
|
|
187
|
-
warnings.push({
|
|
188
|
-
type: 'empty_geometry',
|
|
189
|
-
message: 'Watershed delineation returned empty geometry. USGS StreamStats may not have coverage for this location. Try a nearby point on a mapped stream.',
|
|
190
|
-
severity: 'error',
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
if (result.data?.characteristics?.drainageArea === null || result.data?.characteristics?.drainageArea === undefined) {
|
|
194
|
-
if (result.data?.characteristics) {
|
|
195
|
-
warnings.push({
|
|
196
|
-
type: 'missing_field',
|
|
197
|
-
message: 'Drainage area not returned by StreamStats. This basin characteristic may not be available for this region.',
|
|
198
|
-
field: 'drainageArea',
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Check for null key fields in gage data
|
|
205
|
-
if (toolName.startsWith('get_gage') || toolName.startsWith('get_flood') || toolName.startsWith('get_flow') || toolName.startsWith('get_storm')) {
|
|
206
|
-
if (result.drainageArea === null || result.drainageArea === undefined) {
|
|
207
|
-
if (result.siteName || result.siteId) {
|
|
208
|
-
warnings.push({
|
|
209
|
-
type: 'missing_field',
|
|
210
|
-
message: 'Drainage area is null for this gage. USGS NWIS may not have this metadata. Cross-reference with StreamStats if drainage area is needed.',
|
|
211
|
-
field: 'drainageArea',
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Check for stale NLCD data
|
|
218
|
-
if (toolName === 'export_land_use' || toolName === 'analyze_curve_numbers') {
|
|
219
|
-
warnings.push({
|
|
220
|
-
type: 'data_currency',
|
|
221
|
-
message: 'NLCD land cover data is from 2021 (latest available). For rapidly developing areas, ground-truth current conditions before relying on land use classifications.',
|
|
222
|
-
severity: 'info',
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Check for Atlas 14 metadata issues (Ohio River Basin bug)
|
|
227
|
-
if (toolName === 'get_rainfall_data' || toolName === 'get_idf_curves') {
|
|
228
|
-
const loc = result.location || result.metadata?.location;
|
|
229
|
-
if (loc?.region && /ohio\s*river\s*basin/i.test(loc.region)) {
|
|
230
|
-
// Check if the actual coordinates suggest a different region
|
|
231
|
-
const lat = result.metadata?.lat || result.lat;
|
|
232
|
-
if (lat && lat < 36) {
|
|
233
|
-
warnings.push({
|
|
234
|
-
type: 'metadata_mismatch',
|
|
235
|
-
message: 'NOAA Atlas 14 reports "Ohio River Basin" as the region, but the coordinates may be in a different region. The rainfall data values are still correct — the region label is a known NOAA metadata issue for some locations.',
|
|
236
|
-
severity: 'info',
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
129
|
}
|
|
130
|
+
);
|
|
241
131
|
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// ── Register meta-tools ─────────────────────────────────────────────
|
|
132
|
+
// ── Tool: get_results ────────────────────────────────────────────────
|
|
246
133
|
|
|
247
134
|
server.tool(
|
|
248
|
-
'
|
|
249
|
-
|
|
250
|
-
? 'Find the best GeoTap tools for your question. Describe what you need in plain English and get back the 3-5 most relevant tools with their parameters.'
|
|
251
|
-
: 'Find the best GeoTap tool and action for your question. Describe what you need in plain English. Helpful when you\'re unsure which tool or action to use.',
|
|
135
|
+
'get_results',
|
|
136
|
+
`Check the status of a data collection job and retrieve results. Poll every 10 seconds until status is "completed". When complete, returns the full data summary from all 80+ federal sources.`,
|
|
252
137
|
{
|
|
253
|
-
|
|
254
|
-
maxResults: z.number().optional().describe('Maximum tools to return (default: 5)')
|
|
138
|
+
jobId: z.string().describe('Job ID returned from collect_site_data'),
|
|
255
139
|
},
|
|
256
140
|
async (params) => {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
141
|
+
try {
|
|
142
|
+
const result = await callApi(`/site-analysis/data-collect/${encodeURIComponent(params.jobId)}`, 'GET', {});
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
146
|
+
...result,
|
|
147
|
+
_meta: {
|
|
148
|
+
sources: '80+ US federal agencies (FEMA, USGS, NOAA, EPA, NRCS, USFWS, USACE, DOE, DOT, CDC, Census, and more)',
|
|
149
|
+
retrievedAt: new Date().toISOString(),
|
|
150
|
+
disclaimer: 'Data sourced from US federal agencies via GeoTap. Always verify critical data against authoritative sources before making engineering or regulatory decisions.',
|
|
151
|
+
}
|
|
152
|
+
}, null, 2) }]
|
|
153
|
+
};
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if (error instanceof StructuredApiError) {
|
|
156
|
+
return {
|
|
157
|
+
content: [{ type: 'text', text: JSON.stringify(error.details, null, 2) }],
|
|
158
|
+
isError: true
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
content: [{ type: 'text', text: JSON.stringify({ error: true, message: error.message }, null, 2) }],
|
|
163
|
+
isError: true
|
|
164
|
+
};
|
|
165
|
+
}
|
|
261
166
|
}
|
|
262
167
|
);
|
|
263
168
|
|
|
169
|
+
// ── Tool: get_llms_txt (meta) ────────────────────────────────────────
|
|
170
|
+
|
|
264
171
|
server.tool(
|
|
265
172
|
'get_llms_txt',
|
|
266
|
-
'Get the GeoTap API discovery document
|
|
173
|
+
'Get the GeoTap API discovery document. Returns a structured description of all data sources and capabilities.',
|
|
267
174
|
{},
|
|
268
175
|
async () => {
|
|
269
176
|
try {
|
|
@@ -271,67 +178,14 @@ server.tool(
|
|
|
271
178
|
return { content: [{ type: 'text', text: content }] };
|
|
272
179
|
} catch {
|
|
273
180
|
return {
|
|
274
|
-
content: [{ type: 'text', text: 'llms.txt not found. Visit https://geotapdata.com/llms.txt for
|
|
181
|
+
content: [{ type: 'text', text: 'llms.txt not found. Visit https://geotapdata.com/llms.txt for documentation.' }],
|
|
275
182
|
isError: true
|
|
276
183
|
};
|
|
277
184
|
}
|
|
278
185
|
}
|
|
279
186
|
);
|
|
280
187
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (useLegacyTools) {
|
|
284
|
-
// Legacy mode: register all 85 individual tools (for existing consumers)
|
|
285
|
-
for (const tool of tools) {
|
|
286
|
-
server.tool(
|
|
287
|
-
tool.name,
|
|
288
|
-
tool.description,
|
|
289
|
-
tool.parameters,
|
|
290
|
-
async (params) => handleToolCall(tool.name, tool.endpoint, tool.method, params)
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
console.error(`[geotap] Legacy mode: registered ${tools.length} individual tools`);
|
|
294
|
-
} else {
|
|
295
|
-
// Default: register 12 consolidated tools
|
|
296
|
-
for (const ctool of consolidatedTools) {
|
|
297
|
-
server.tool(
|
|
298
|
-
ctool.name,
|
|
299
|
-
ctool.description,
|
|
300
|
-
ctool.parameters,
|
|
301
|
-
async (params) => {
|
|
302
|
-
const { action, ...restParams } = params;
|
|
303
|
-
|
|
304
|
-
// Resolve consolidated action → legacy tool
|
|
305
|
-
const legacyName = ctool._actionMap[action];
|
|
306
|
-
if (!legacyName) {
|
|
307
|
-
return {
|
|
308
|
-
content: [{ type: 'text', text: JSON.stringify({
|
|
309
|
-
error: true,
|
|
310
|
-
message: `Unknown action "${action}" for tool "${ctool.name}".`,
|
|
311
|
-
validActions: Object.keys(ctool._actionMap),
|
|
312
|
-
fix: [`Use one of: ${Object.keys(ctool._actionMap).join(', ')}`],
|
|
313
|
-
}, null, 2) }],
|
|
314
|
-
isError: true
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const legacyTool = legacyToolMap.get(legacyName);
|
|
319
|
-
if (!legacyTool) {
|
|
320
|
-
return {
|
|
321
|
-
content: [{ type: 'text', text: JSON.stringify({
|
|
322
|
-
error: true,
|
|
323
|
-
message: `Internal routing error: legacy tool "${legacyName}" not found.`,
|
|
324
|
-
}, null, 2) }],
|
|
325
|
-
isError: true
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
return handleToolCall(legacyName, legacyTool.endpoint, legacyTool.method, restParams);
|
|
330
|
-
}
|
|
331
|
-
);
|
|
332
|
-
}
|
|
333
|
-
console.error(`[geotap] Consolidated mode: registered ${consolidatedTools.length} tools (set GEOTAP_LEGACY_TOOLS=true for 85 individual tools)`);
|
|
334
|
-
}
|
|
188
|
+
console.error(`[geotap] v3.0.0 — 2 tools (collect_site_data, get_results) + 1 meta-tool (get_llms_txt)`);
|
|
335
189
|
|
|
336
190
|
// Start server
|
|
337
191
|
const transport = new StdioServerTransport();
|