@cyanheads/earthquake-mcp-server 0.1.1
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/CLAUDE.md +388 -0
- package/Dockerfile +98 -0
- package/LICENSE +201 -0
- package/README.md +291 -0
- package/changelog/0.1.x/0.1.0.md +19 -0
- package/changelog/0.1.x/0.1.1.md +20 -0
- package/changelog/template.md +119 -0
- package/dist/config/server-config.d.ts +14 -0
- package/dist/config/server-config.d.ts.map +1 -0
- package/dist/config/server-config.js +43 -0
- package/dist/config/server-config.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server/resources/definitions/earthquake-event.resource.d.ts +40 -0
- package/dist/mcp-server/resources/definitions/earthquake-event.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/earthquake-event.resource.js +27 -0
- package/dist/mcp-server/resources/definitions/earthquake-event.resource.js.map +1 -0
- package/dist/mcp-server/resources/definitions/earthquake-feed.resource.d.ts +24 -0
- package/dist/mcp-server/resources/definitions/earthquake-feed.resource.d.ts.map +1 -0
- package/dist/mcp-server/resources/definitions/earthquake-feed.resource.js +76 -0
- package/dist/mcp-server/resources/definitions/earthquake-feed.resource.js.map +1 -0
- package/dist/mcp-server/tools/definitions/earthquake-count.tool.d.ts +48 -0
- package/dist/mcp-server/tools/definitions/earthquake-count.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/earthquake-count.tool.js +170 -0
- package/dist/mcp-server/tools/definitions/earthquake-count.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/earthquake-get-event.tool.d.ts +46 -0
- package/dist/mcp-server/tools/definitions/earthquake-get-event.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/earthquake-get-event.tool.js +50 -0
- package/dist/mcp-server/tools/definitions/earthquake-get-event.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/earthquake-get-feed.tool.d.ts +61 -0
- package/dist/mcp-server/tools/definitions/earthquake-get-feed.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/earthquake-get-feed.tool.js +88 -0
- package/dist/mcp-server/tools/definitions/earthquake-get-feed.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/earthquake-search.tool.d.ts +89 -0
- package/dist/mcp-server/tools/definitions/earthquake-search.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/earthquake-search.tool.js +199 -0
- package/dist/mcp-server/tools/definitions/earthquake-search.tool.js.map +1 -0
- package/dist/mcp-server/tools/schemas.d.ts +60 -0
- package/dist/mcp-server/tools/schemas.d.ts.map +1 -0
- package/dist/mcp-server/tools/schemas.js +83 -0
- package/dist/mcp-server/tools/schemas.js.map +1 -0
- package/dist/services/emsc/emsc-service.d.ts +30 -0
- package/dist/services/emsc/emsc-service.d.ts.map +1 -0
- package/dist/services/emsc/emsc-service.js +170 -0
- package/dist/services/emsc/emsc-service.js.map +1 -0
- package/dist/services/usgs/types.d.ts +136 -0
- package/dist/services/usgs/types.d.ts.map +1 -0
- package/dist/services/usgs/types.js +6 -0
- package/dist/services/usgs/types.js.map +1 -0
- package/dist/services/usgs/usgs-service.d.ts +39 -0
- package/dist/services/usgs/usgs-service.d.ts.map +1 -0
- package/dist/services/usgs/usgs-service.js +283 -0
- package/dist/services/usgs/usgs-service.js.map +1 -0
- package/package.json +78 -0
- package/server.json +99 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emsc-service.d.ts","sourceRoot":"","sources":["../../../src/services/emsc/emsc-service.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAE/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAOrE,OAAO,KAAK,EACV,eAAe,EACf,qBAAqB,EAItB,MAAM,kBAAkB,CAAC;AA2C1B,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAGjC,OAAO,EAAE,SAAS,EAClB,QAAQ,EAAE,cAAc,EACxB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM;IAMnB,iCAAiC;IACjC,YAAY,CACV,MAAM,EAAE,qBAAqB,EAC7B,GAAG,EAAE,OAAO,GACX,OAAO,CAAC;QACT,MAAM,EAAE,eAAe,EAAE,CAAC;QAC1B,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IA8CF,qCAAqC;IACrC,WAAW,CACT,MAAM,EAAE,qBAAqB,EAC7B,GAAG,EAAE,OAAO,GACX,OAAO,CAAC;QACT,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,YAAY,EAAE,OAAO,CAAC;KACvB,CAAC;IA8CF,qFAAqF;IACrF,OAAO,CAAC,cAAc;CAsBvB;AAMD,wBAAgB,eAAe,CAC7B,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,cAAc,EACvB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB,IAAI,CAEN;AAED,wBAAgB,cAAc,IAAI,WAAW,CAK5C"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview EMSC (European-Mediterranean Seismological Centre) FDSN event API client.
|
|
3
|
+
* @module services/emsc/emsc-service
|
|
4
|
+
*/
|
|
5
|
+
import { serviceUnavailable } from '@cyanheads/mcp-ts-core/errors';
|
|
6
|
+
import { fetchWithTimeout, httpErrorFromResponse, requestContextService, withRetry, } from '@cyanheads/mcp-ts-core/utils';
|
|
7
|
+
/** 1 degree of latitude ≈ 111.2 km. */
|
|
8
|
+
const KM_PER_DEGREE = 111.2;
|
|
9
|
+
/** Normalize an EMSC GeoJSON feature to the shared EarthquakeEvent domain type. */
|
|
10
|
+
function normalizeEmscFeature(f) {
|
|
11
|
+
const p = f.properties;
|
|
12
|
+
const [lon, lat, depth] = f.geometry.coordinates;
|
|
13
|
+
const id = p.unid ?? f.id ?? `emsc-unknown-${Date.now()}`;
|
|
14
|
+
const place = p.flynn_region ?? 'Unknown location';
|
|
15
|
+
const magType = p.magtype ?? 'unknown';
|
|
16
|
+
const time = p.time ?? new Date(0).toISOString();
|
|
17
|
+
const updated = p.lastupdate ?? time;
|
|
18
|
+
// EMSC: properties.lat/lon may duplicate geometry coordinates; use them when present
|
|
19
|
+
const actualLat = typeof p.lat === 'number' ? p.lat : lat;
|
|
20
|
+
const actualLon = typeof p.lon === 'number' ? p.lon : lon;
|
|
21
|
+
const actualDepth = typeof p.depth === 'number' ? p.depth : depth;
|
|
22
|
+
return {
|
|
23
|
+
id,
|
|
24
|
+
title: `M ${p.mag ?? '?'} - ${place}`,
|
|
25
|
+
magnitude: p.mag ?? 0,
|
|
26
|
+
magnitude_type: magType,
|
|
27
|
+
time,
|
|
28
|
+
updated,
|
|
29
|
+
place,
|
|
30
|
+
latitude: actualLat,
|
|
31
|
+
longitude: actualLon,
|
|
32
|
+
depth_km: actualDepth,
|
|
33
|
+
// EMSC does not provide USGS-specific impact fields
|
|
34
|
+
felt: null,
|
|
35
|
+
cdi: null,
|
|
36
|
+
mmi: null,
|
|
37
|
+
alert: null,
|
|
38
|
+
tsunami: 0,
|
|
39
|
+
significance: null,
|
|
40
|
+
status: 'reviewed', // EMSC events are generally reviewed
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export class EmscService {
|
|
44
|
+
baseUrl;
|
|
45
|
+
timeoutMs;
|
|
46
|
+
constructor(_config, _storage, emscBaseUrl, timeoutMs) {
|
|
47
|
+
this.baseUrl = emscBaseUrl.replace(/\/$/, '');
|
|
48
|
+
this.timeoutMs = timeoutMs;
|
|
49
|
+
}
|
|
50
|
+
/** Query EMSC FDSN event API. */
|
|
51
|
+
searchEvents(params, ctx) {
|
|
52
|
+
const query = this.buildFdsnQuery(params);
|
|
53
|
+
const url = `${this.baseUrl}/fdsnws/event/1/query?format=json&${query}`;
|
|
54
|
+
const reqCtx = requestContextService.createRequestContext({
|
|
55
|
+
operation: 'EmscService.searchEvents',
|
|
56
|
+
parentContext: {
|
|
57
|
+
requestId: ctx.requestId,
|
|
58
|
+
traceId: ctx.traceId,
|
|
59
|
+
tenantId: ctx.tenantId,
|
|
60
|
+
timestamp: new Date().toISOString(),
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
return withRetry(async () => {
|
|
64
|
+
const response = await fetchWithTimeout(url, this.timeoutMs, reqCtx, {
|
|
65
|
+
signal: ctx.signal,
|
|
66
|
+
headers: { Accept: 'application/json' },
|
|
67
|
+
});
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
throw await httpErrorFromResponse(response, { service: 'EMSC FDSN', data: { url } });
|
|
70
|
+
}
|
|
71
|
+
const text = await response.text();
|
|
72
|
+
if (/^\s*<(!DOCTYPE\s+html|html[\s>])/i.test(text)) {
|
|
73
|
+
throw serviceUnavailable('EMSC returned HTML instead of JSON.', { url });
|
|
74
|
+
}
|
|
75
|
+
const data = JSON.parse(text);
|
|
76
|
+
const events = data.features.map(normalizeEmscFeature);
|
|
77
|
+
return {
|
|
78
|
+
events,
|
|
79
|
+
count: events.length,
|
|
80
|
+
};
|
|
81
|
+
}, {
|
|
82
|
+
operation: 'EmscService.searchEvents',
|
|
83
|
+
context: reqCtx,
|
|
84
|
+
baseDelayMs: 1000,
|
|
85
|
+
signal: ctx.signal,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/** Count events matching a query. */
|
|
89
|
+
countEvents(params, ctx) {
|
|
90
|
+
const query = this.buildFdsnQuery(params);
|
|
91
|
+
const url = `${this.baseUrl}/fdsnws/event/1/count?format=json&${query}`;
|
|
92
|
+
const reqCtx = requestContextService.createRequestContext({
|
|
93
|
+
operation: 'EmscService.countEvents',
|
|
94
|
+
parentContext: {
|
|
95
|
+
requestId: ctx.requestId,
|
|
96
|
+
traceId: ctx.traceId,
|
|
97
|
+
tenantId: ctx.tenantId,
|
|
98
|
+
timestamp: new Date().toISOString(),
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
return withRetry(async () => {
|
|
102
|
+
const response = await fetchWithTimeout(url, this.timeoutMs, reqCtx, {
|
|
103
|
+
signal: ctx.signal,
|
|
104
|
+
headers: { Accept: 'application/json' },
|
|
105
|
+
});
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
throw await httpErrorFromResponse(response, { service: 'EMSC Count', data: { url } });
|
|
108
|
+
}
|
|
109
|
+
const text = await response.text();
|
|
110
|
+
if (/^\s*<(!DOCTYPE\s+html|html[\s>])/i.test(text)) {
|
|
111
|
+
throw serviceUnavailable('EMSC returned HTML instead of JSON.', { url });
|
|
112
|
+
}
|
|
113
|
+
const data = JSON.parse(text);
|
|
114
|
+
const EMSC_LIMIT = 20000;
|
|
115
|
+
return {
|
|
116
|
+
count: data.count,
|
|
117
|
+
maxAllowed: null, // EMSC count endpoint does not return maxAllowed
|
|
118
|
+
exceedsLimit: data.count > EMSC_LIMIT,
|
|
119
|
+
};
|
|
120
|
+
}, {
|
|
121
|
+
operation: 'EmscService.countEvents',
|
|
122
|
+
context: reqCtx,
|
|
123
|
+
baseDelayMs: 1000,
|
|
124
|
+
signal: ctx.signal,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/** Build FDSN query string from params, converting km radius to degrees for EMSC. */
|
|
128
|
+
buildFdsnQuery(params) {
|
|
129
|
+
const q = new URLSearchParams();
|
|
130
|
+
if (params.startTime)
|
|
131
|
+
q.set('starttime', params.startTime);
|
|
132
|
+
if (params.endTime)
|
|
133
|
+
q.set('endtime', params.endTime);
|
|
134
|
+
if (params.minMagnitude != null)
|
|
135
|
+
q.set('minmagnitude', String(params.minMagnitude));
|
|
136
|
+
if (params.maxMagnitude != null)
|
|
137
|
+
q.set('maxmagnitude', String(params.maxMagnitude));
|
|
138
|
+
if (params.latitude != null)
|
|
139
|
+
q.set('latitude', String(params.latitude));
|
|
140
|
+
if (params.longitude != null)
|
|
141
|
+
q.set('longitude', String(params.longitude));
|
|
142
|
+
if (params.radiusKm != null) {
|
|
143
|
+
// EMSC only supports maxradius in degrees — convert from km
|
|
144
|
+
const degrees = params.radiusKm / KM_PER_DEGREE;
|
|
145
|
+
q.set('maxradius', degrees.toFixed(4));
|
|
146
|
+
}
|
|
147
|
+
if (params.minDepthKm != null)
|
|
148
|
+
q.set('mindepth', String(params.minDepthKm));
|
|
149
|
+
if (params.maxDepthKm != null)
|
|
150
|
+
q.set('maxdepth', String(params.maxDepthKm));
|
|
151
|
+
// EMSC does not support alertlevel, minfelt, minsig — silently omit
|
|
152
|
+
if (params.limit != null)
|
|
153
|
+
q.set('limit', String(params.limit));
|
|
154
|
+
if (params.orderBy)
|
|
155
|
+
q.set('orderby', params.orderBy);
|
|
156
|
+
return q.toString();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// --- Init/accessor pattern ---
|
|
160
|
+
let _service;
|
|
161
|
+
export function initEmscService(config, storage, emscBaseUrl, timeoutMs) {
|
|
162
|
+
_service = new EmscService(config, storage, emscBaseUrl, timeoutMs);
|
|
163
|
+
}
|
|
164
|
+
export function getEmscService() {
|
|
165
|
+
if (!_service) {
|
|
166
|
+
throw new Error('EmscService not initialized — call initEmscService() in setup()');
|
|
167
|
+
}
|
|
168
|
+
return _service;
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=emsc-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emsc-service.js","sourceRoot":"","sources":["../../../src/services/emsc/emsc-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared domain types for USGS and EMSC earthquake data.
|
|
3
|
+
* @module services/usgs/types
|
|
4
|
+
*/
|
|
5
|
+
/** Normalized earthquake event returned by both USGS and EMSC sources. */
|
|
6
|
+
export interface EarthquakeEvent {
|
|
7
|
+
alert: 'green' | 'yellow' | 'orange' | 'red' | null;
|
|
8
|
+
cdi: number | null;
|
|
9
|
+
depth_km: number;
|
|
10
|
+
detail_url?: string;
|
|
11
|
+
event_url?: string;
|
|
12
|
+
felt: number | null;
|
|
13
|
+
id: string;
|
|
14
|
+
latitude: number;
|
|
15
|
+
longitude: number;
|
|
16
|
+
magnitude: number;
|
|
17
|
+
magnitude_type: string;
|
|
18
|
+
mmi: number | null;
|
|
19
|
+
place: string;
|
|
20
|
+
significance: number | null;
|
|
21
|
+
status: 'automatic' | 'reviewed' | 'deleted';
|
|
22
|
+
time: string;
|
|
23
|
+
title: string;
|
|
24
|
+
tsunami: number;
|
|
25
|
+
updated: string;
|
|
26
|
+
}
|
|
27
|
+
/** Raw USGS GeoJSON feature properties from list/query responses. */
|
|
28
|
+
export interface UsgsFeatureProperties {
|
|
29
|
+
alert?: string | null;
|
|
30
|
+
cdi?: number | null;
|
|
31
|
+
code?: string | null;
|
|
32
|
+
detail?: string | null;
|
|
33
|
+
dmin?: number | null;
|
|
34
|
+
felt?: number | null;
|
|
35
|
+
gap?: number | null;
|
|
36
|
+
ids?: string | null;
|
|
37
|
+
mag?: number | null;
|
|
38
|
+
magType?: string | null;
|
|
39
|
+
mmi?: number | null;
|
|
40
|
+
net?: string | null;
|
|
41
|
+
nst?: number | null;
|
|
42
|
+
place?: string | null;
|
|
43
|
+
products?: Record<string, unknown>;
|
|
44
|
+
rms?: number | null;
|
|
45
|
+
sig?: number | null;
|
|
46
|
+
sources?: string | null;
|
|
47
|
+
status?: string | null;
|
|
48
|
+
time?: number | null;
|
|
49
|
+
title?: string | null;
|
|
50
|
+
tsunami?: number | null;
|
|
51
|
+
type?: string | null;
|
|
52
|
+
types?: string | null;
|
|
53
|
+
tz?: number | null;
|
|
54
|
+
updated?: number | null;
|
|
55
|
+
url?: string | null;
|
|
56
|
+
}
|
|
57
|
+
/** Raw USGS GeoJSON feature. */
|
|
58
|
+
export interface UsgsFeature {
|
|
59
|
+
geometry: {
|
|
60
|
+
type: 'Point';
|
|
61
|
+
coordinates: [number, number, number];
|
|
62
|
+
};
|
|
63
|
+
id: string;
|
|
64
|
+
properties: UsgsFeatureProperties;
|
|
65
|
+
type: 'Feature';
|
|
66
|
+
}
|
|
67
|
+
/** Raw USGS GeoJSON FeatureCollection response. */
|
|
68
|
+
export interface UsgsFeatureCollection {
|
|
69
|
+
bbox?: number[];
|
|
70
|
+
features: UsgsFeature[];
|
|
71
|
+
metadata: {
|
|
72
|
+
generated: number;
|
|
73
|
+
url: string;
|
|
74
|
+
title: string;
|
|
75
|
+
status: number;
|
|
76
|
+
api: string;
|
|
77
|
+
count: number;
|
|
78
|
+
};
|
|
79
|
+
type: 'FeatureCollection';
|
|
80
|
+
}
|
|
81
|
+
/** Raw USGS count response. */
|
|
82
|
+
export interface UsgsCountResponse {
|
|
83
|
+
count: number;
|
|
84
|
+
maxAllowed: number;
|
|
85
|
+
}
|
|
86
|
+
/** Raw EMSC event properties. */
|
|
87
|
+
export interface EmscEventProperties {
|
|
88
|
+
auth?: string | null;
|
|
89
|
+
depth?: number | null;
|
|
90
|
+
evtype?: string | null;
|
|
91
|
+
flynn_region?: string | null;
|
|
92
|
+
lastupdate?: string;
|
|
93
|
+
lat?: number | null;
|
|
94
|
+
lon?: number | null;
|
|
95
|
+
mag?: number | null;
|
|
96
|
+
magtype?: string | null;
|
|
97
|
+
time?: string;
|
|
98
|
+
unid?: string;
|
|
99
|
+
}
|
|
100
|
+
/** Raw EMSC GeoJSON feature. */
|
|
101
|
+
export interface EmscFeature {
|
|
102
|
+
geometry: {
|
|
103
|
+
type: 'Point';
|
|
104
|
+
coordinates: [number, number, number];
|
|
105
|
+
};
|
|
106
|
+
id?: string;
|
|
107
|
+
properties: EmscEventProperties;
|
|
108
|
+
type: 'Feature';
|
|
109
|
+
}
|
|
110
|
+
/** Raw EMSC JSON response (format=json). */
|
|
111
|
+
export interface EmscFeatureCollection {
|
|
112
|
+
features: EmscFeature[];
|
|
113
|
+
type: 'FeatureCollection';
|
|
114
|
+
}
|
|
115
|
+
/** Raw EMSC count response. */
|
|
116
|
+
export interface EmscCountResponse {
|
|
117
|
+
count: number;
|
|
118
|
+
}
|
|
119
|
+
/** Query parameters shared by both USGS and EMSC search/count endpoints. */
|
|
120
|
+
export interface EarthquakeQueryParams {
|
|
121
|
+
alertLevel?: string;
|
|
122
|
+
endTime?: string;
|
|
123
|
+
latitude?: number;
|
|
124
|
+
limit?: number;
|
|
125
|
+
longitude?: number;
|
|
126
|
+
maxDepthKm?: number;
|
|
127
|
+
maxMagnitude?: number;
|
|
128
|
+
minDepthKm?: number;
|
|
129
|
+
minFelt?: number;
|
|
130
|
+
minMagnitude?: number;
|
|
131
|
+
minSignificance?: number;
|
|
132
|
+
orderBy?: string;
|
|
133
|
+
radiusKm?: number;
|
|
134
|
+
startTime?: string;
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/services/usgs/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/services/usgs/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview USGS Earthquake Hazards Program API client — real-time feeds and FDSN event queries.
|
|
3
|
+
* @module services/usgs/usgs-service
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@cyanheads/mcp-ts-core';
|
|
6
|
+
import type { AppConfig } from '@cyanheads/mcp-ts-core/config';
|
|
7
|
+
import type { StorageService } from '@cyanheads/mcp-ts-core/storage';
|
|
8
|
+
import type { EarthquakeEvent, EarthquakeQueryParams } from './types.js';
|
|
9
|
+
export declare class UsgsService {
|
|
10
|
+
private readonly baseUrl;
|
|
11
|
+
private readonly timeoutMs;
|
|
12
|
+
constructor(_config: AppConfig, _storage: StorageService, usgsBaseUrl: string, timeoutMs: number);
|
|
13
|
+
/** Fetch a pre-computed USGS real-time feed. */
|
|
14
|
+
getFeed(magnitudeTier: 'all' | '1.0' | '2.5' | '4.5' | 'significant', timeWindow: 'hour' | 'day' | 'week' | 'month', ctx: Context): Promise<{
|
|
15
|
+
events: EarthquakeEvent[];
|
|
16
|
+
generatedAt: string;
|
|
17
|
+
count: number;
|
|
18
|
+
feedUrl: string;
|
|
19
|
+
}>;
|
|
20
|
+
/** Query USGS FDSN event API. */
|
|
21
|
+
searchEvents(params: EarthquakeQueryParams, ctx: Context): Promise<{
|
|
22
|
+
events: EarthquakeEvent[];
|
|
23
|
+
count: number;
|
|
24
|
+
totalCount?: number;
|
|
25
|
+
}>;
|
|
26
|
+
/** Fetch a single event by USGS event ID. */
|
|
27
|
+
getEvent(eventId: string, ctx: Context): Promise<EarthquakeEvent>;
|
|
28
|
+
/** Count events matching a query. */
|
|
29
|
+
countEvents(params: EarthquakeQueryParams, ctx: Context): Promise<{
|
|
30
|
+
count: number;
|
|
31
|
+
maxAllowed: number | null;
|
|
32
|
+
exceedsLimit: boolean;
|
|
33
|
+
}>;
|
|
34
|
+
/** Build FDSN query string from params. */
|
|
35
|
+
private buildFdsnQuery;
|
|
36
|
+
}
|
|
37
|
+
export declare function initUsgsService(config: AppConfig, storage: StorageService, usgsBaseUrl: string, timeoutMs: number): void;
|
|
38
|
+
export declare function getUsgsService(): UsgsService;
|
|
39
|
+
//# sourceMappingURL=usgs-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usgs-service.d.ts","sourceRoot":"","sources":["../../../src/services/usgs/usgs-service.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAE/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAOrE,OAAO,KAAK,EACV,eAAe,EACf,qBAAqB,EAItB,MAAM,YAAY,CAAC;AA8CpB,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAGjC,OAAO,EAAE,SAAS,EAClB,QAAQ,EAAE,cAAc,EACxB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM;IAMnB,gDAAgD;IAChD,OAAO,CACL,aAAa,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,aAAa,EAC5D,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,EAC7C,GAAG,EAAE,OAAO,GACX,OAAO,CAAC;QAAE,MAAM,EAAE,eAAe,EAAE,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAqD9F,iCAAiC;IACjC,YAAY,CACV,MAAM,EAAE,qBAAqB,EAC7B,GAAG,EAAE,OAAO,GACX,OAAO,CAAC;QACT,MAAM,EAAE,eAAe,EAAE,CAAC;QAC1B,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IAiEF,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC;IAqEjE,qCAAqC;IACrC,WAAW,CACT,MAAM,EAAE,qBAAqB,EAC7B,GAAG,EAAE,OAAO,GACX,OAAO,CAAC;QACT,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,YAAY,EAAE,OAAO,CAAC;KACvB,CAAC;IAiDF,2CAA2C;IAC3C,OAAO,CAAC,cAAc;CAoBvB;AAMD,wBAAgB,eAAe,CAC7B,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,cAAc,EACvB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB,IAAI,CAEN;AAED,wBAAgB,cAAc,IAAI,WAAW,CAK5C"}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview USGS Earthquake Hazards Program API client — real-time feeds and FDSN event queries.
|
|
3
|
+
* @module services/usgs/usgs-service
|
|
4
|
+
*/
|
|
5
|
+
import { notFound, serviceUnavailable } from '@cyanheads/mcp-ts-core/errors';
|
|
6
|
+
import { fetchWithTimeout, httpErrorFromResponse, requestContextService, withRetry, } from '@cyanheads/mcp-ts-core/utils';
|
|
7
|
+
/** Convert a USGS epoch-millisecond timestamp to an ISO 8601 string. */
|
|
8
|
+
function epochMsToIso(ms) {
|
|
9
|
+
if (ms == null)
|
|
10
|
+
return new Date(0).toISOString();
|
|
11
|
+
return new Date(ms).toISOString();
|
|
12
|
+
}
|
|
13
|
+
/** Normalize a USGS GeoJSON feature to the shared EarthquakeEvent domain type. */
|
|
14
|
+
function normalizeUsgsFeature(f) {
|
|
15
|
+
const p = f.properties;
|
|
16
|
+
const [lon, lat, depth] = f.geometry.coordinates;
|
|
17
|
+
const rawStatus = p.status ?? 'automatic';
|
|
18
|
+
const status = rawStatus === 'reviewed' || rawStatus === 'deleted' ? rawStatus : 'automatic';
|
|
19
|
+
const rawAlert = p.alert;
|
|
20
|
+
const alert = rawAlert === 'green' || rawAlert === 'yellow' || rawAlert === 'orange' || rawAlert === 'red'
|
|
21
|
+
? rawAlert
|
|
22
|
+
: null;
|
|
23
|
+
return {
|
|
24
|
+
id: f.id,
|
|
25
|
+
title: p.title ?? `M ${p.mag ?? '?'} - ${p.place ?? 'Unknown location'}`,
|
|
26
|
+
magnitude: p.mag ?? 0,
|
|
27
|
+
magnitude_type: p.magType ?? 'unknown',
|
|
28
|
+
time: epochMsToIso(p.time),
|
|
29
|
+
updated: epochMsToIso(p.updated),
|
|
30
|
+
place: p.place ?? 'Unknown location',
|
|
31
|
+
latitude: lat,
|
|
32
|
+
longitude: lon,
|
|
33
|
+
depth_km: depth,
|
|
34
|
+
felt: p.felt ?? null,
|
|
35
|
+
cdi: p.cdi ?? null,
|
|
36
|
+
mmi: p.mmi ?? null,
|
|
37
|
+
alert,
|
|
38
|
+
tsunami: p.tsunami ?? 0,
|
|
39
|
+
significance: p.sig ?? null,
|
|
40
|
+
status,
|
|
41
|
+
...(p.url ? { event_url: p.url } : {}),
|
|
42
|
+
...(p.detail ? { detail_url: p.detail } : {}),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export class UsgsService {
|
|
46
|
+
baseUrl;
|
|
47
|
+
timeoutMs;
|
|
48
|
+
constructor(_config, _storage, usgsBaseUrl, timeoutMs) {
|
|
49
|
+
this.baseUrl = usgsBaseUrl.replace(/\/$/, '');
|
|
50
|
+
this.timeoutMs = timeoutMs;
|
|
51
|
+
}
|
|
52
|
+
/** Fetch a pre-computed USGS real-time feed. */
|
|
53
|
+
getFeed(magnitudeTier, timeWindow, ctx) {
|
|
54
|
+
const feedUrl = `${this.baseUrl}/earthquakes/feed/v1.0/summary/${magnitudeTier}_${timeWindow}.geojson`;
|
|
55
|
+
const reqCtx = requestContextService.createRequestContext({
|
|
56
|
+
operation: 'UsgsService.getFeed',
|
|
57
|
+
parentContext: {
|
|
58
|
+
requestId: ctx.requestId,
|
|
59
|
+
traceId: ctx.traceId,
|
|
60
|
+
tenantId: ctx.tenantId,
|
|
61
|
+
timestamp: new Date().toISOString(),
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
return withRetry(async () => {
|
|
65
|
+
const response = await fetchWithTimeout(feedUrl, this.timeoutMs, reqCtx, {
|
|
66
|
+
signal: ctx.signal,
|
|
67
|
+
headers: { Accept: 'application/json' },
|
|
68
|
+
});
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
throw await httpErrorFromResponse(response, {
|
|
71
|
+
service: 'USGS Feed',
|
|
72
|
+
data: { feedUrl },
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const text = await response.text();
|
|
76
|
+
if (/^\s*<(!DOCTYPE\s+html|html[\s>])/i.test(text)) {
|
|
77
|
+
throw serviceUnavailable('USGS returned HTML instead of GeoJSON — likely rate-limited or a CDN error.', { feedUrl });
|
|
78
|
+
}
|
|
79
|
+
const data = JSON.parse(text);
|
|
80
|
+
const events = data.features.map(normalizeUsgsFeature);
|
|
81
|
+
return {
|
|
82
|
+
events,
|
|
83
|
+
generatedAt: epochMsToIso(data.metadata.generated),
|
|
84
|
+
count: data.metadata.count,
|
|
85
|
+
feedUrl: data.metadata.url ?? feedUrl,
|
|
86
|
+
};
|
|
87
|
+
}, {
|
|
88
|
+
operation: 'UsgsService.getFeed',
|
|
89
|
+
context: reqCtx,
|
|
90
|
+
baseDelayMs: 1000,
|
|
91
|
+
signal: ctx.signal,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/** Query USGS FDSN event API. */
|
|
95
|
+
searchEvents(params, ctx) {
|
|
96
|
+
const query = this.buildFdsnQuery(params);
|
|
97
|
+
const url = `${this.baseUrl}/fdsnws/event/1/query?format=geojson&${query}`;
|
|
98
|
+
const reqCtx = requestContextService.createRequestContext({
|
|
99
|
+
operation: 'UsgsService.searchEvents',
|
|
100
|
+
parentContext: {
|
|
101
|
+
requestId: ctx.requestId,
|
|
102
|
+
traceId: ctx.traceId,
|
|
103
|
+
tenantId: ctx.tenantId,
|
|
104
|
+
timestamp: new Date().toISOString(),
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
return withRetry(async () => {
|
|
108
|
+
const response = await fetchWithTimeout(url, this.timeoutMs, reqCtx, {
|
|
109
|
+
signal: ctx.signal,
|
|
110
|
+
headers: { Accept: 'application/json' },
|
|
111
|
+
});
|
|
112
|
+
if (response.status === 400) {
|
|
113
|
+
// USGS returns plain-text "Error 400: ..." for overly broad queries
|
|
114
|
+
const body = await response.text();
|
|
115
|
+
const broadMatch = /(\d+) matching events exceeds search limit/.exec(body);
|
|
116
|
+
if (broadMatch?.[1]) {
|
|
117
|
+
const totalCount = parseInt(broadMatch[1], 10);
|
|
118
|
+
throw Object.assign(new Error(`Query matches ${totalCount} events, exceeding the 20,000-event limit. ` +
|
|
119
|
+
'Narrow time range, raise min_magnitude, or add location filters.'), { code: -32602, data: { reason: 'query_too_broad', totalCount } });
|
|
120
|
+
}
|
|
121
|
+
throw await httpErrorFromResponse(response, { service: 'USGS FDSN' });
|
|
122
|
+
}
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
throw await httpErrorFromResponse(response, { service: 'USGS FDSN', data: { url } });
|
|
125
|
+
}
|
|
126
|
+
const text = await response.text();
|
|
127
|
+
if (/^\s*<(!DOCTYPE\s+html|html[\s>])/i.test(text)) {
|
|
128
|
+
throw serviceUnavailable('USGS returned HTML instead of GeoJSON.', { url });
|
|
129
|
+
}
|
|
130
|
+
const data = JSON.parse(text);
|
|
131
|
+
const events = data.features.map(normalizeUsgsFeature);
|
|
132
|
+
const requestedLimit = params.limit ?? 100;
|
|
133
|
+
return {
|
|
134
|
+
events,
|
|
135
|
+
count: events.length,
|
|
136
|
+
...(data.metadata.count > requestedLimit ? { totalCount: data.metadata.count } : {}),
|
|
137
|
+
};
|
|
138
|
+
}, {
|
|
139
|
+
operation: 'UsgsService.searchEvents',
|
|
140
|
+
context: reqCtx,
|
|
141
|
+
baseDelayMs: 1000,
|
|
142
|
+
signal: ctx.signal,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/** Fetch a single event by USGS event ID. */
|
|
146
|
+
getEvent(eventId, ctx) {
|
|
147
|
+
const url = `${this.baseUrl}/fdsnws/event/1/query?eventid=${encodeURIComponent(eventId)}&format=geojson`;
|
|
148
|
+
const reqCtx = requestContextService.createRequestContext({
|
|
149
|
+
operation: 'UsgsService.getEvent',
|
|
150
|
+
parentContext: {
|
|
151
|
+
requestId: ctx.requestId,
|
|
152
|
+
traceId: ctx.traceId,
|
|
153
|
+
tenantId: ctx.tenantId,
|
|
154
|
+
timestamp: new Date().toISOString(),
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
return withRetry(async () => {
|
|
158
|
+
const response = await fetchWithTimeout(url, this.timeoutMs, reqCtx, {
|
|
159
|
+
signal: ctx.signal,
|
|
160
|
+
headers: { Accept: 'application/json' },
|
|
161
|
+
});
|
|
162
|
+
if (response.status === 404) {
|
|
163
|
+
throw notFound(`No earthquake event found for ID "${eventId}". Verify the ID from a feed or search result.`, { eventId });
|
|
164
|
+
}
|
|
165
|
+
if (!response.ok) {
|
|
166
|
+
// USGS sometimes returns 400 with "Error 404" body for unknown IDs
|
|
167
|
+
const body = await response.text();
|
|
168
|
+
if (/Error 404/i.test(body)) {
|
|
169
|
+
throw notFound(`No earthquake event found for ID "${eventId}". Verify the ID from a feed or search result.`, { eventId });
|
|
170
|
+
}
|
|
171
|
+
throw serviceUnavailable(`USGS returned HTTP ${response.status} for event lookup.`, {
|
|
172
|
+
eventId,
|
|
173
|
+
status: response.status,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
const text = await response.text();
|
|
177
|
+
if (/^\s*<(!DOCTYPE\s+html|html[\s>])/i.test(text)) {
|
|
178
|
+
throw serviceUnavailable('USGS returned HTML instead of GeoJSON.', { eventId });
|
|
179
|
+
}
|
|
180
|
+
const data = JSON.parse(text);
|
|
181
|
+
if (!data.features || data.features.length === 0) {
|
|
182
|
+
throw notFound(`No earthquake event found for ID "${eventId}". Verify the ID from a feed or search result.`, { eventId });
|
|
183
|
+
}
|
|
184
|
+
const feature = data.features[0];
|
|
185
|
+
if (!feature) {
|
|
186
|
+
throw notFound(`No event data returned for ID "${eventId}".`, { eventId });
|
|
187
|
+
}
|
|
188
|
+
return normalizeUsgsFeature(feature);
|
|
189
|
+
}, {
|
|
190
|
+
operation: 'UsgsService.getEvent',
|
|
191
|
+
context: reqCtx,
|
|
192
|
+
baseDelayMs: 1000,
|
|
193
|
+
signal: ctx.signal,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
/** Count events matching a query. */
|
|
197
|
+
countEvents(params, ctx) {
|
|
198
|
+
const query = this.buildFdsnQuery(params);
|
|
199
|
+
const url = `${this.baseUrl}/fdsnws/event/1/count?format=geojson&${query}`;
|
|
200
|
+
const reqCtx = requestContextService.createRequestContext({
|
|
201
|
+
operation: 'UsgsService.countEvents',
|
|
202
|
+
parentContext: {
|
|
203
|
+
requestId: ctx.requestId,
|
|
204
|
+
traceId: ctx.traceId,
|
|
205
|
+
tenantId: ctx.tenantId,
|
|
206
|
+
timestamp: new Date().toISOString(),
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
return withRetry(async () => {
|
|
210
|
+
const response = await fetchWithTimeout(url, this.timeoutMs, reqCtx, {
|
|
211
|
+
signal: ctx.signal,
|
|
212
|
+
headers: { Accept: 'application/json' },
|
|
213
|
+
});
|
|
214
|
+
if (!response.ok) {
|
|
215
|
+
throw await httpErrorFromResponse(response, {
|
|
216
|
+
service: 'USGS Count',
|
|
217
|
+
data: { url },
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
const text = await response.text();
|
|
221
|
+
if (/^\s*<(!DOCTYPE\s+html|html[\s>])/i.test(text)) {
|
|
222
|
+
throw serviceUnavailable('USGS returned HTML instead of JSON.', { url });
|
|
223
|
+
}
|
|
224
|
+
const data = JSON.parse(text);
|
|
225
|
+
const maxAllowed = data.maxAllowed ?? 20000;
|
|
226
|
+
return {
|
|
227
|
+
count: data.count,
|
|
228
|
+
maxAllowed,
|
|
229
|
+
exceedsLimit: data.count > maxAllowed,
|
|
230
|
+
};
|
|
231
|
+
}, {
|
|
232
|
+
operation: 'UsgsService.countEvents',
|
|
233
|
+
context: reqCtx,
|
|
234
|
+
baseDelayMs: 1000,
|
|
235
|
+
signal: ctx.signal,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/** Build FDSN query string from params. */
|
|
239
|
+
buildFdsnQuery(params) {
|
|
240
|
+
const q = new URLSearchParams();
|
|
241
|
+
if (params.startTime)
|
|
242
|
+
q.set('starttime', params.startTime);
|
|
243
|
+
if (params.endTime)
|
|
244
|
+
q.set('endtime', params.endTime);
|
|
245
|
+
if (params.minMagnitude != null)
|
|
246
|
+
q.set('minmagnitude', String(params.minMagnitude));
|
|
247
|
+
if (params.maxMagnitude != null)
|
|
248
|
+
q.set('maxmagnitude', String(params.maxMagnitude));
|
|
249
|
+
if (params.latitude != null)
|
|
250
|
+
q.set('latitude', String(params.latitude));
|
|
251
|
+
if (params.longitude != null)
|
|
252
|
+
q.set('longitude', String(params.longitude));
|
|
253
|
+
if (params.radiusKm != null)
|
|
254
|
+
q.set('maxradiuskm', String(params.radiusKm));
|
|
255
|
+
if (params.minDepthKm != null)
|
|
256
|
+
q.set('mindepth', String(params.minDepthKm));
|
|
257
|
+
if (params.maxDepthKm != null)
|
|
258
|
+
q.set('maxdepth', String(params.maxDepthKm));
|
|
259
|
+
if (params.alertLevel)
|
|
260
|
+
q.set('alertlevel', params.alertLevel);
|
|
261
|
+
if (params.minFelt != null)
|
|
262
|
+
q.set('minfelt', String(params.minFelt));
|
|
263
|
+
if (params.minSignificance != null)
|
|
264
|
+
q.set('minsig', String(params.minSignificance));
|
|
265
|
+
if (params.limit != null)
|
|
266
|
+
q.set('limit', String(params.limit));
|
|
267
|
+
if (params.orderBy)
|
|
268
|
+
q.set('orderby', params.orderBy);
|
|
269
|
+
return q.toString();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// --- Init/accessor pattern ---
|
|
273
|
+
let _service;
|
|
274
|
+
export function initUsgsService(config, storage, usgsBaseUrl, timeoutMs) {
|
|
275
|
+
_service = new UsgsService(config, storage, usgsBaseUrl, timeoutMs);
|
|
276
|
+
}
|
|
277
|
+
export function getUsgsService() {
|
|
278
|
+
if (!_service) {
|
|
279
|
+
throw new Error('UsgsService not initialized — call initUsgsService() in setup()');
|
|
280
|
+
}
|
|
281
|
+
return _service;
|
|
282
|
+
}
|
|
283
|
+
//# sourceMappingURL=usgs-service.js.map
|