@pipeworx/mcp-usgs-volcano 0.1.0 → 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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  USGS Volcano MCP — Volcano Hazards Program HANS-public feed (no auth)
4
4
 
5
- Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 250+ live data sources.
5
+ Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 1170+ live data sources.
6
6
 
7
7
  ## Tools
8
8
 
@@ -23,7 +23,7 @@ Add to your MCP client (Claude Desktop, Cursor, Windsurf, etc.):
23
23
  }
24
24
  ```
25
25
 
26
- Or connect to the full Pipeworx gateway for access to all 250+ data sources:
26
+ Or connect to the full Pipeworx gateway for access to all 1170+ data sources:
27
27
 
28
28
  ```json
29
29
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipeworx/mcp-usgs-volcano",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "USGS Volcano MCP — Volcano Hazards Program HANS-public feed (no auth)",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.pipeworx-io/usgs-volcano",
4
4
  "title": "Usgs Volcano",
5
5
  "description": "USGS Volcano MCP — Volcano Hazards Program HANS-public feed (no auth)",
6
- "version": "0.1.0",
6
+ "version": "0.1.1",
7
7
  "websiteUrl": "https://pipeworx.io/packs/usgs-volcano",
8
8
  "repository": {
9
9
  "url": "https://github.com/pipeworx-io/mcp-usgs-volcano",
package/src/index.ts CHANGED
@@ -75,16 +75,15 @@ const tools: McpToolExport['tools'] = [
75
75
  ];
76
76
 
77
77
  async function callTool(name: string, args: Record<string, unknown>): Promise<unknown> {
78
- switch (name) {
79
- case 'list_volcanoes':
80
- return listVolcanoes(args.observatory as string | undefined);
81
- case 'list_elevated':
82
- return listElevated();
83
- case 'list_notices':
84
- return listNotices(args.volcano_slug as string | undefined, (args.limit as number) ?? 50);
85
- default:
86
- throw new Error(`Unknown tool: ${name}`);
87
- }
78
+ // list_elevated re-pointed to the renamed getCAPElevated action (2026-06-11).
79
+ if (name === 'list_elevated') return await listElevated();
80
+
81
+ // Re-pointed 2026-07: USGS HANS renamed getAllVolcanoes -> getMonitoredVolcanoes
82
+ // (each record carries the volcano's latest notice) and dropped the standalone
83
+ // notices action, so both tools are served from that one endpoint now.
84
+ if (name === 'list_volcanoes') return await listVolcanoes(args.observatory as string | undefined);
85
+ if (name === 'list_notices') return await listNotices(args.volcano_slug as string | undefined, (args.limit as number) ?? 20);
86
+ throw new Error(`Unknown tool: ${name}`);
88
87
  }
89
88
 
90
89
  async function hansFetch<T>(path: string): Promise<T> {
@@ -96,109 +95,122 @@ async function hansFetch<T>(path: string): Promise<T> {
96
95
  return res.json() as Promise<T>;
97
96
  }
98
97
 
99
- interface VolcanoRecord {
98
+ // getMonitoredVolcanoes returns one record per monitored volcano, each carrying
99
+ // its latest alert + notice.
100
+ interface MonitoredRecord {
100
101
  volcano_name?: string;
101
- url_name?: string;
102
- observatory?: string;
102
+ vnum?: string;
103
+ volcano_cd?: string;
104
+ obs_abbr?: string;
105
+ obs_fullname?: string;
103
106
  alert_level?: string;
104
107
  color_code?: string;
105
- obs_abbr?: string;
106
- latitude?: number;
107
- longitude?: number;
108
- elevation?: number;
109
- vnum?: string;
110
- synonyms?: string;
111
- alert_url?: string;
112
- alert_message?: string;
113
- date_modified?: string;
108
+ sent_utc?: string;
109
+ sent_unixtime?: number;
110
+ notice_type_cd?: string;
111
+ notice_identifier?: string;
112
+ notice_url?: string;
113
+ notice_data?: string;
114
114
  }
115
115
 
116
- function normalizeVolcano(v: VolcanoRecord) {
116
+ function slugify(name?: string): string | null {
117
+ return name ? name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') : null;
118
+ }
119
+
120
+ function normalizeVolcano(v: MonitoredRecord) {
117
121
  return {
118
122
  name: v.volcano_name ?? null,
119
- slug: v.url_name ?? null,
120
- observatory: v.observatory ?? v.obs_abbr ?? null,
123
+ slug: slugify(v.volcano_name),
124
+ observatory: v.obs_abbr ?? null,
125
+ observatory_full: v.obs_fullname ?? null,
121
126
  alert_level: v.alert_level ?? null,
122
127
  color_code: v.color_code ?? null,
123
- latitude: v.latitude ?? null,
124
- longitude: v.longitude ?? null,
125
- elevation_m: v.elevation ?? null,
126
128
  vnum: v.vnum ?? null,
127
- synonyms: v.synonyms ?? null,
128
- alert_message: v.alert_message ?? null,
129
- alert_url: v.alert_url ?? null,
130
- modified: v.date_modified ?? null,
129
+ latest_notice_type: v.notice_type_cd ?? null,
130
+ latest_notice_url: v.notice_url ?? null,
131
+ updated_utc: v.sent_utc ?? null,
131
132
  };
132
133
  }
133
134
 
134
135
  async function listVolcanoes(observatory: string | undefined) {
135
- const data = await hansFetch<VolcanoRecord[]>('/volcano/getAllVolcanoes');
136
+ const data = await hansFetch<MonitoredRecord[]>('/volcano/getMonitoredVolcanoes');
136
137
  const all = (data ?? []).map(normalizeVolcano);
137
138
  const filtered = observatory
138
- ? all.filter(
139
- (v) => v.observatory?.toLowerCase() === observatory.toLowerCase(),
140
- )
139
+ ? all.filter((v) => v.observatory?.toLowerCase() === observatory.toLowerCase())
141
140
  : all;
142
- return {
143
- total: all.length,
144
- returned: filtered.length,
145
- volcanoes: filtered,
146
- };
141
+ return { total: all.length, returned: filtered.length, volcanoes: filtered };
142
+ }
143
+
144
+ // USGS HANS renamed elevated/getElevatedVolcanoes → volcano/getCAPElevated
145
+ // (CAP = Common Alerting Protocol) with new field names. Re-pointed 2026-06-11.
146
+ interface CapElevatedRecord {
147
+ volcano_name_appended?: string;
148
+ latitude?: number;
149
+ longitude?: number;
150
+ vnum?: string;
151
+ elevation_meters?: number;
152
+ obs_fullname?: string;
153
+ alert_level?: string;
154
+ color_code?: string;
155
+ cap_certainty?: string;
156
+ cap_severity?: string;
157
+ cap_urgency?: string;
158
+ notice_identifier?: string;
159
+ sent_date_cap?: string;
147
160
  }
148
161
 
149
162
  async function listElevated() {
150
- const data = await hansFetch<VolcanoRecord[]>('/elevated/getElevatedVolcanoes');
151
- const list = (data ?? []).map(normalizeVolcano);
163
+ const data = await hansFetch<CapElevatedRecord[]>('/volcano/getCAPElevated');
164
+ const list = (data ?? []).map((v) => ({
165
+ volcano_name: v.volcano_name_appended ?? null,
166
+ alert_level: v.alert_level ?? null,
167
+ color_code: v.color_code ?? null,
168
+ observatory: v.obs_fullname ?? null,
169
+ latitude: v.latitude ?? null,
170
+ longitude: v.longitude ?? null,
171
+ elevation_m: v.elevation_meters ?? null,
172
+ vnum: v.vnum ?? null,
173
+ cap_certainty: v.cap_certainty ?? null,
174
+ cap_severity: v.cap_severity ?? null,
175
+ cap_urgency: v.cap_urgency ?? null,
176
+ notice_id: v.notice_identifier ?? null,
177
+ sent_at: v.sent_date_cap ?? null,
178
+ }));
152
179
  return {
153
180
  count: list.length,
154
- note: 'All volcanoes currently above Normal/Green status (advisories, watches, or warnings).',
181
+ note: 'Volcanoes currently above Normal/Green status (advisory / watch / warning), with CAP alert details. Source: USGS HANS getCAPElevated.',
155
182
  volcanoes: list,
156
183
  };
157
184
  }
158
185
 
159
- interface NoticeRecord {
160
- notice_id?: string;
161
- volcano_name?: string;
162
- url_name?: string;
163
- observatory?: string;
164
- message_type?: string;
165
- alert_level?: string;
166
- color_code?: string;
167
- sent_utc?: string;
168
- date_modified?: string;
169
- url?: string;
170
- summary?: string;
171
- full_message?: string;
172
- }
173
-
174
- function normalizeNotice(n: NoticeRecord) {
186
+ function normalizeNotice(n: MonitoredRecord) {
175
187
  return {
176
- id: n.notice_id ?? null,
177
188
  volcano: n.volcano_name ?? null,
178
- slug: n.url_name ?? null,
179
- observatory: n.observatory ?? null,
180
- type: n.message_type ?? null,
189
+ slug: slugify(n.volcano_name),
190
+ observatory: n.obs_abbr ?? null,
191
+ type: n.notice_type_cd ?? null,
192
+ notice_id: n.notice_identifier ?? null,
181
193
  alert_level: n.alert_level ?? null,
182
194
  color_code: n.color_code ?? null,
183
- sent_utc: n.sent_utc ?? n.date_modified ?? null,
184
- summary: n.summary ?? null,
185
- message: n.full_message ?? null,
186
- url: n.url ?? null,
195
+ sent_utc: n.sent_utc ?? null,
196
+ url: n.notice_url ?? null,
197
+ message: n.notice_data ?? null,
187
198
  };
188
199
  }
189
200
 
190
201
  async function listNotices(volcanoSlug: string | undefined, limit: number) {
191
- // The HANS notices endpoint accepts the slug directly when filtering one volcano.
192
- const path = volcanoSlug
193
- ? `/notice/getVolcanoNotices?volcano=${encodeURIComponent(volcanoSlug)}`
194
- : '/notice/getNotices';
195
- const data = await hansFetch<NoticeRecord[]>(path);
196
- const list = (data ?? []).slice(0, Math.max(1, limit)).map(normalizeNotice);
197
- return {
198
- volcano_slug: volcanoSlug ?? null,
199
- count: list.length,
200
- notices: list,
201
- };
202
+ // No standalone notices action survives the HANS rename; each monitored
203
+ // volcano carries its latest notice, so derive the list from
204
+ // getMonitoredVolcanoes (sorted newest-first, optionally filtered by volcano).
205
+ const data = await hansFetch<MonitoredRecord[]>('/volcano/getMonitoredVolcanoes');
206
+ let list = (data ?? []).filter((n) => n.notice_identifier || n.notice_url);
207
+ if (volcanoSlug) {
208
+ const s = volcanoSlug.toLowerCase();
209
+ list = list.filter((n) => slugify(n.volcano_name)?.includes(s) || n.volcano_cd?.toLowerCase() === s);
210
+ }
211
+ list.sort((a, b) => (b.sent_unixtime ?? 0) - (a.sent_unixtime ?? 0));
212
+ const notices = list.slice(0, Math.max(1, limit)).map(normalizeNotice);
213
+ return { volcano_slug: volcanoSlug ?? null, count: notices.length, notices };
202
214
  }
203
215
 
204
216
  export default { tools, callTool, meter: { credits: 1 } } satisfies McpToolExport;