@cyanheads/noaa-spaceweather-mcp-server 0.1.13 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/AGENTS.md +6 -4
  2. package/CLAUDE.md +6 -4
  3. package/README.md +26 -17
  4. package/changelog/0.1.x/0.1.14.md +32 -0
  5. package/changelog/0.2.x/0.2.0.md +21 -0
  6. package/dist/mcp-server/tools/definitions/get-alerts.tool.d.ts +19 -1
  7. package/dist/mcp-server/tools/definitions/get-alerts.tool.d.ts.map +1 -1
  8. package/dist/mcp-server/tools/definitions/get-alerts.tool.js +281 -41
  9. package/dist/mcp-server/tools/definitions/get-alerts.tool.js.map +1 -1
  10. package/dist/mcp-server/tools/definitions/get-aurora-forecast.tool.d.ts +9 -1
  11. package/dist/mcp-server/tools/definitions/get-aurora-forecast.tool.d.ts.map +1 -1
  12. package/dist/mcp-server/tools/definitions/get-aurora-forecast.tool.js +91 -52
  13. package/dist/mcp-server/tools/definitions/get-aurora-forecast.tool.js.map +1 -1
  14. package/dist/mcp-server/tools/definitions/get-conditions.tool.d.ts +27 -8
  15. package/dist/mcp-server/tools/definitions/get-conditions.tool.d.ts.map +1 -1
  16. package/dist/mcp-server/tools/definitions/get-conditions.tool.js +234 -38
  17. package/dist/mcp-server/tools/definitions/get-conditions.tool.js.map +1 -1
  18. package/dist/mcp-server/tools/definitions/get-kp-index.tool.d.ts +7 -1
  19. package/dist/mcp-server/tools/definitions/get-kp-index.tool.d.ts.map +1 -1
  20. package/dist/mcp-server/tools/definitions/get-kp-index.tool.js +21 -6
  21. package/dist/mcp-server/tools/definitions/get-kp-index.tool.js.map +1 -1
  22. package/dist/mcp-server/tools/definitions/get-solar-activity.tool.d.ts +32 -2
  23. package/dist/mcp-server/tools/definitions/get-solar-activity.tool.d.ts.map +1 -1
  24. package/dist/mcp-server/tools/definitions/get-solar-activity.tool.js +243 -39
  25. package/dist/mcp-server/tools/definitions/get-solar-activity.tool.js.map +1 -1
  26. package/dist/mcp-server/tools/definitions/get-solar-wind.tool.d.ts +17 -1
  27. package/dist/mcp-server/tools/definitions/get-solar-wind.tool.d.ts.map +1 -1
  28. package/dist/mcp-server/tools/definitions/get-solar-wind.tool.js +150 -21
  29. package/dist/mcp-server/tools/definitions/get-solar-wind.tool.js.map +1 -1
  30. package/dist/mcp-server/tools/definitions/index.d.ts +111 -14
  31. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -1
  32. package/dist/services/space-weather/space-weather-service.d.ts +76 -3
  33. package/dist/services/space-weather/space-weather-service.d.ts.map +1 -1
  34. package/dist/services/space-weather/space-weather-service.js +539 -82
  35. package/dist/services/space-weather/space-weather-service.js.map +1 -1
  36. package/dist/services/space-weather/types.d.ts +177 -21
  37. package/dist/services/space-weather/types.d.ts.map +1 -1
  38. package/package.json +3 -3
  39. package/server.json +3 -3
@@ -5,6 +5,154 @@
5
5
  import { tool, z } from '@cyanheads/mcp-ts-core';
6
6
  import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
7
7
  import { getSpaceWeatherService } from '../../../services/space-weather/space-weather-service.js';
8
+ /**
9
+ * Why a record was left out of an `active_only=true` result, in the precedence order a
10
+ * record is attributed to — each record is counted under the first reason that fires, so
11
+ * the counts partition the feed rather than overlapping.
12
+ *
13
+ * The window leads because it decides which records were candidates at all, and product
14
+ * type follows because a Summary is never in force whatever its body says. Then the
15
+ * reasons that name a *replacement* — this record is itself a cancellation, a later
16
+ * record cancelled it, a later record superseded it — and only last the generic
17
+ * "its stated end passed".
18
+ *
19
+ * That ordering is deliberate: a superseded Watch has usually outlived its own forecast
20
+ * days too, so ranking elapsed validity higher would report the whole Watch chain as
21
+ * merely stale and never mention that a newer forecast replaced it. The specific reason
22
+ * is the one a caller can act on — it points at a successor record.
23
+ */
24
+ const EXCLUSION_REASONS = [
25
+ 'agedOut',
26
+ 'productType',
27
+ 'cancellationRecord',
28
+ 'cancelledBySerial',
29
+ 'superseded',
30
+ 'validityElapsed',
31
+ ];
32
+ /** Product types that can be in force; everything else is informational or unparsed. */
33
+ const IN_FORCE_TYPES = new Set([
34
+ 'Warning',
35
+ 'Watch',
36
+ 'Alert',
37
+ ]);
38
+ /**
39
+ * The record's stated end as an epoch, or null when nothing usable is stated. A validity
40
+ * line SWPC wrote as prose survives parsing as raw text (see `parseValidity`), and an end
41
+ * that cannot be read is not evidence the product has finished — those return null and
42
+ * the record is treated as in force.
43
+ */
44
+ function endMs(alert) {
45
+ if (alert.validTo === null)
46
+ return null;
47
+ const ms = Date.parse(alert.validTo);
48
+ return Number.isNaN(ms) ? null : ms;
49
+ }
50
+ /**
51
+ * Resolve each cancellation to the single record it names, as indices into `all`.
52
+ *
53
+ * Serials are per-message-code counters, so the match is scoped to the cancelling
54
+ * record's own code — the same serial under another code is a different product. SWPC
55
+ * reuses a serial within a code on a corrected reissue, so "Original Issue Time:" picks
56
+ * between them when it names one of the candidates; when it names none (a formatting
57
+ * drift upstream), the nearest preceding record under that code and serial is taken
58
+ * rather than missing the cancellation entirely.
59
+ *
60
+ * Resolving one target per cancellation is what keeps a cancellation from clearing its
61
+ * whole message code: a single code cycles CONTINUED → CANCEL → CONTINUED within minutes,
62
+ * and the records either side of the cancellation are still in force.
63
+ */
64
+ function resolveCancelledBySerial(all) {
65
+ const cancelled = new Set();
66
+ for (const cancellation of all) {
67
+ if (!cancellation.cancelled || !cancellation.cancelsSerialNumber)
68
+ continue;
69
+ const cancelIssuedMs = Date.parse(cancellation.issueDatetime);
70
+ const candidates = all
71
+ .map((alert, index) => ({ alert, index }))
72
+ .filter(({ alert }) => alert !== cancellation &&
73
+ alert.messageCode === cancellation.messageCode &&
74
+ alert.serialNumber === cancellation.cancelsSerialNumber &&
75
+ Date.parse(alert.issueDatetime) < cancelIssuedMs)
76
+ .sort((a, b) => Date.parse(a.alert.issueDatetime) - Date.parse(b.alert.issueDatetime));
77
+ if (candidates.length === 0)
78
+ continue;
79
+ const named = cancellation.cancelsOriginalIssueDatetime;
80
+ // The two times come from different feed fields — the body's minute-precision
81
+ // "Issue Time:" versus the record's sub-second `issue_datetime` — so they agree only
82
+ // to the minute.
83
+ const exact = named
84
+ ? candidates.filter(({ alert }) => sameMinute(alert.issueDatetime, named))
85
+ : [];
86
+ const target = (exact.length > 0 ? exact : candidates).at(-1);
87
+ if (target)
88
+ cancelled.add(target.index);
89
+ }
90
+ return cancelled;
91
+ }
92
+ /** True when two ISO 8601 instants fall in the same UTC minute. */
93
+ function sameMinute(a, b) {
94
+ const aMs = Date.parse(a);
95
+ const bMs = Date.parse(b);
96
+ if (Number.isNaN(aMs) || Number.isNaN(bMs))
97
+ return false;
98
+ return Math.floor(aMs / 60_000) === Math.floor(bMs / 60_000);
99
+ }
100
+ /**
101
+ * Indices of every record a later supersede-carrying record has replaced — all of them
102
+ * but the newest by issue time.
103
+ *
104
+ * The rule keys on the line rather than the message code because the line says any and
105
+ * all: the live Watches are sequential revisions of one three-day forecast, issued under
106
+ * whichever `WATA*` code matches the level they predict, so scoping per code returns two
107
+ * conflicting outlooks for the same day. The newest is chosen across the whole feed, not
108
+ * just the records the window admitted — a superseding record outside the caller's
109
+ * lookback still happened.
110
+ *
111
+ * An unreadable issue time sorts oldest rather than unbeatable: every `>` comparison
112
+ * against `NaN` is false, so seeding it at 0 is what stops one undated record from
113
+ * standing as the newest carrier and superseding every genuine Watch behind it.
114
+ */
115
+ function resolveSuperseded(all) {
116
+ const issuedMs = ({ alert }) => Date.parse(alert.issueDatetime) || 0;
117
+ const carriers = all
118
+ .map((alert, index) => ({ alert, index }))
119
+ .filter(({ alert }) => alert.supersedes);
120
+ if (carriers.length <= 1)
121
+ return new Set();
122
+ const newest = carriers.reduce((best, current) => issuedMs(current) > issuedMs(best) ? current : best);
123
+ return new Set(carriers.filter(({ index }) => index !== newest.index).map(({ index }) => index));
124
+ }
125
+ /**
126
+ * The first reason a record is not in force, or null when it is. A product is in force
127
+ * until something in the feed says otherwise — a stated end that has passed, a
128
+ * cancellation naming it, or a later product that supersedes it.
129
+ */
130
+ function exclusionReason(alert, index, ctx) {
131
+ const end = endMs(alert);
132
+ const issuedMs = Date.parse(alert.issueDatetime);
133
+ // `max_age_hours` bounds how far back to look for candidates; it is not itself a
134
+ // statement about whether a product is in force. A product whose end is still ahead
135
+ // stays in scope however old its issue time — without that, the default window drops a
136
+ // Watch hours before the storm day it forecasts finishes. An issue time that cannot be
137
+ // read counts as outside the window: nothing places it inside one.
138
+ const outsideWindow = Number.isNaN(issuedMs) || issuedMs < ctx.cutoffMs;
139
+ const endAhead = end !== null && end >= ctx.nowMs;
140
+ if (outsideWindow && !endAhead)
141
+ return 'agedOut';
142
+ if (!IN_FORCE_TYPES.has(alert.productType))
143
+ return 'productType';
144
+ // A cancellation carries the cancelled product's own type and no validity window, so
145
+ // neither check above excludes it.
146
+ if (alert.cancelled)
147
+ return 'cancellationRecord';
148
+ if (ctx.cancelledBySerial.has(index))
149
+ return 'cancelledBySerial';
150
+ if (ctx.superseded.has(index))
151
+ return 'superseded';
152
+ if (end !== null && end < ctx.nowMs)
153
+ return 'validityElapsed';
154
+ return null;
155
+ }
8
156
  const AlertSchema = z
9
157
  .object({
10
158
  productId: z
@@ -25,7 +173,11 @@ const AlertSchema = z
25
173
  .describe('NOAA scale stated in the message body, e.g. "G1", "R2", "S1"; null when the product states none.'),
26
174
  cancelled: z
27
175
  .boolean()
28
- .describe('True when this record cancels a previously issued product ("CANCEL WARNING:"/"CANCEL ALERT:" headline) rather than being in force. Always false when active_only=true, which excludes cancellations; set active_only=false to see them.'),
176
+ .describe('True when this record cancels a previously issued product ("CANCEL WARNING:"/"CANCEL WATCH:"/"CANCEL ALERT:" headline) rather than being in force. Always false when active_only=true, which excludes both the cancellation record and the product it names — a cancelled product carries cancelled: false itself, so this flag does not identify one. Set active_only=false to see cancellations and what they cancelled.'),
177
+ serialNumber: z
178
+ .string()
179
+ .nullable()
180
+ .describe('The record\'s SWPC "Serial Number:" value, e.g. "1125"; null when the body carries no such line. A per-message-code counter, not a globally unique ID — it repeats across codes and within one code on a corrected reissue, so quote it together with messageCode. It is the key the "Cancel Serial Number:", "Extension to Serial Number:", and "Continuation of Serial Number:" lines in message point at, which is what makes those chains navigable.'),
29
181
  phenomenon: z
30
182
  .string()
31
183
  .describe('Short phenomenon name derived from the body\'s NOAA scale letter, e.g. "Geomagnetic", "Radio Blackout", "Solar Radiation".'),
@@ -37,43 +189,55 @@ const AlertSchema = z
37
189
  validTo: z
38
190
  .string()
39
191
  .nullable()
40
- .describe('Validity-window end as ISO 8601 UTC, parsed from the message body ("Valid To", "Now Valid Until", or "End Time"); null when the product carries no end line.'),
192
+ .describe('Validity-window end as ISO 8601 UTC. Read from the message body\'s "Valid To", "Now Valid Until", or "End Time" label when the product states one. A Watch states none, so its end is derived from the "Highest Storm Level Predicted by Day:" list instead: the instant the last listed UTC day forecasting a storm ends (a trailing "None" day is a forecast of quiet, not coverage), so a Watch listing Sep 17 as its last storm day ends at 2026-09-18T00:00:00Z. Null when nothing in the body states or implies an end — a point-in-time Alert, or a Watch forecasting no storm on any listed day.'),
41
193
  message: z.string().describe('Full plain-text message body.'),
42
194
  })
43
195
  .describe('One SWPC alert, watch, warning, or summary.');
44
196
  export const getAlerts = tool('noaa_spaceweather_get_alerts', {
45
197
  title: 'Get Space Weather Alerts',
46
198
  description: 'Active SWPC alerts, watches, and warnings — parsed into structured records with product type, ' +
47
- 'NOAA scale and level, issue time, validity window, and plain text. Covers geomagnetic storms, ' +
48
- 'radio blackouts, and radiation storms. With active_only=false, also returns informational ' +
49
- 'summaries, expired notices, and cancellations. max_age_hours controls how far back to look ' +
50
- '(default 48 h); the SWPC feed keeps all historical records and has no built-in expiry.',
199
+ 'NOAA scale and level, issue time, serial number, validity window, and plain text. Covers ' +
200
+ 'geomagnetic storms, radio blackouts, and radiation storms. With active_only=false, also ' +
201
+ 'returns informational summaries, expired notices, and cancellations. max_age_hours controls ' +
202
+ 'how far back to look for candidates (default 48 h) — under active_only=true it does not cut ' +
203
+ 'off a product whose validity end is still in the future, and under active_only=false it is a ' +
204
+ 'literal age cutoff. The SWPC feed keeps all historical records and has no built-in expiry.',
51
205
  annotations: { readOnlyHint: true, openWorldHint: true, idempotentHint: true },
52
206
  input: z.object({
53
207
  active_only: z
54
208
  .boolean()
55
209
  .default(true)
56
- .describe('When true (default), return only in-force Warnings, Watches, and Alerts; exclude Summaries, Other, expired products, and cancellation notices. Set false to return all products, including cancellations (flagged by the cancelled field).'),
210
+ .describe('When true (default), return only in-force Warnings, Watches, and Alerts. Excluded: Summaries and unrecognized products; products whose validity end has passed; cancellation notices; any product a later cancellation names by serial; and all but the newest record carrying the "THIS SUPERSEDES ANY/ALL PRIOR WATCHES IN EFFECT" line. Counts per reason ride in the exclusions enrichment field. Set false to return every product in the window, cancellations included (flagged by the cancelled field).'),
57
211
  max_age_hours: z
58
212
  .number()
59
213
  .min(1)
60
214
  .max(720)
61
215
  .default(48)
62
- .describe('Maximum age of alerts to return, in hours (default 48). The SWPC feed retains all historical records — this window prevents returning weeks of historical notices as "active."'),
216
+ .describe('How far back to look for products, in hours (default 48). The SWPC feed retains all historical records, so this bounds the candidate set rather than declaring what is current. Under active_only=true a product whose validity end is still in the future is returned even when its issue time falls outside this window — a multi-day Watch would otherwise disappear while a day it forecasts a storm for is still running. Under active_only=false it is literal and cuts at exactly the requested age.'),
63
217
  }),
64
218
  output: z.object({
65
219
  alerts: z.array(AlertSchema).describe('Matching SWPC alert/watch/warning records.'),
66
220
  totalCount: z.number().describe('Count of records in the alerts array.'),
221
+ activeOnly: z
222
+ .boolean()
223
+ .describe('Echo of the active_only input: true when the records are the in-force set, false when they are every product in the window. Distinguishes an empty in-force result from an empty feed window.'),
67
224
  fetchedAt: z.string().describe('ISO 8601 timestamp of when this data was fetched.'),
68
225
  }),
69
226
  errors: [
70
227
  {
71
228
  reason: 'feed_unavailable',
72
229
  code: JsonRpcErrorCode.ServiceUnavailable,
73
- when: 'SWPC endpoint returns non-OK status or times out after retries.',
230
+ when: 'SWPC feed returns 5xx or 429, times out, or answers with a body that is not parseable JSON. Retried before failing.',
74
231
  retryable: true,
75
232
  recovery: 'Retry in 30–60 seconds; SWPC feeds occasionally lag during high-activity events.',
76
233
  },
234
+ {
235
+ reason: 'feed_moved',
236
+ code: JsonRpcErrorCode.ServiceUnavailable,
237
+ when: 'SWPC feed path returns a permanent 4xx (404, 410, 401, 403). Fails in one attempt.',
238
+ retryable: false,
239
+ recovery: 'Retrying will not help — the SWPC feed path no longer resolves or no longer has the expected shape; the feed URL needs updating against SWPC current inventory.',
240
+ },
77
241
  ],
78
242
  async handler(input, ctx) {
79
243
  ctx.log.info('Fetching SWPC alerts', {
@@ -82,38 +246,58 @@ export const getAlerts = tool('noaa_spaceweather_get_alerts', {
82
246
  });
83
247
  const svc = getSpaceWeatherService();
84
248
  const all = await svc.getAlerts(ctx);
85
- // Apply recency window first — the feed keeps all historical records with no
86
- // expiry; without this, active_only=true returns weeks of historical notices.
87
- // Compare as Date objects (epoch) — string comparison would silently fail when
88
- // issueDatetime and the ISO cutoff don't share the exact same format.
249
+ // Compare as epochs — string comparison would silently fail when issueDatetime and
250
+ // the ISO cutoff don't share the exact same format.
89
251
  const nowMs = Date.now();
90
252
  const cutoffMs = nowMs - input.max_age_hours * 60 * 60 * 1000;
91
- const recents = all.filter((a) => new Date(a.issueDatetime).getTime() >= cutoffMs);
92
- // active_only admits only in-force Warnings/Watches/Alerts. A product whose
93
- // parsed validTo has already elapsed is dropped; Watch/Alert notices carry no
94
- // validTo (point-in-time), and a validTo we cannot parse is treated as in-force
95
- // — both fall back to the recency window above, so an "active" query never
96
- // silently hides a warning whose end time is missing or unreadable.
97
- const filtered = input.active_only
98
- ? recents.filter((a) => {
99
- if (a.productType !== 'Warning' &&
100
- a.productType !== 'Watch' &&
101
- a.productType !== 'Alert') {
102
- return false;
103
- }
104
- // A cancellation carries the cancelled product's own type and no validity
105
- // window, so neither check above excludes it — it must be dropped explicitly.
106
- if (a.cancelled)
107
- return false;
108
- if (a.validTo === null)
109
- return true;
110
- const validToMs = new Date(a.validTo).getTime();
111
- return Number.isNaN(validToMs) || validToMs >= nowMs;
112
- })
113
- : recents;
253
+ let filtered;
254
+ let excluded;
255
+ if (input.active_only) {
256
+ // Both cross-record rules read the whole feed, not just the records the window
257
+ // admitted: a cancellation or a superseding Watch issued outside the caller's
258
+ // lookback still happened.
259
+ const filterCtx = {
260
+ nowMs,
261
+ cutoffMs,
262
+ cancelledBySerial: resolveCancelledBySerial(all),
263
+ superseded: resolveSuperseded(all),
264
+ };
265
+ const counts = Object.fromEntries(EXCLUSION_REASONS.map((r) => [r, 0]));
266
+ filtered = [];
267
+ all.forEach((alert, index) => {
268
+ const reason = exclusionReason(alert, index, filterCtx);
269
+ if (reason)
270
+ counts[reason] += 1;
271
+ else
272
+ filtered.push(alert);
273
+ });
274
+ // Zeros across the board would tell a caller nothing it can act on; the window
275
+ // echo below already says filtering was applied.
276
+ if (filtered.length < all.length)
277
+ excluded = counts;
278
+ ctx.enrich({
279
+ appliedWindowHours: input.max_age_hours,
280
+ appliedCutoff: new Date(cutoffMs).toISOString(),
281
+ });
282
+ // Written separately rather than spread in conditionally: enrich accumulates, and
283
+ // an absent field must stay absent rather than arrive as an explicit undefined.
284
+ if (excluded)
285
+ ctx.enrich({ exclusions: excluded });
286
+ }
287
+ else {
288
+ // A literal history window — nothing is filtered by reason, so no counts.
289
+ filtered = all.filter((a) => Date.parse(a.issueDatetime) >= cutoffMs);
290
+ }
114
291
  if (filtered.length === 0) {
292
+ // ctx.enrich.notice is last-wins, so the whole empty-state message is composed
293
+ // once here rather than appended to across branches.
294
+ const excludedCount = excluded
295
+ ? Object.values(excluded).reduce((sum, n) => sum + n, 0)
296
+ : undefined;
115
297
  ctx.enrich.notice(input.active_only
116
- ? 'No active alerts, watches, or warnings. Set active_only=false to include summaries.'
298
+ ? excludedCount
299
+ ? `No active alerts, watches, or warnings — all ${excludedCount} products in the feed were excluded; see exclusions for the breakdown, or set active_only=false to see them.`
300
+ : 'No active alerts, watches, or warnings. Set active_only=false to include summaries.'
117
301
  : 'No space weather products issued in the requested window.');
118
302
  }
119
303
  return {
@@ -124,6 +308,7 @@ export const getAlerts = tool('noaa_spaceweather_get_alerts', {
124
308
  level: a.level,
125
309
  noaaScale: a.noaaScale,
126
310
  cancelled: a.cancelled,
311
+ serialNumber: a.serialNumber,
127
312
  phenomenon: a.phenomenon,
128
313
  issueDatetime: a.issueDatetime,
129
314
  validFrom: a.validFrom,
@@ -131,18 +316,69 @@ export const getAlerts = tool('noaa_spaceweather_get_alerts', {
131
316
  message: a.message,
132
317
  })),
133
318
  totalCount: filtered.length,
319
+ activeOnly: input.active_only,
134
320
  fetchedAt: new Date().toISOString(),
135
321
  };
136
322
  },
137
323
  enrichment: {
138
- notice: z.string().optional().describe('Status notice when no alerts are active.'),
324
+ notice: z.string().optional().describe('Status notice when no products were returned.'),
325
+ appliedWindowHours: z
326
+ .number()
327
+ .optional()
328
+ .describe('The max_age_hours window as applied, echoed so a caller can see what bounded the candidate set. Present only under active_only=true, where a future validity end can keep an older product in scope.'),
329
+ appliedCutoff: z
330
+ .string()
331
+ .optional()
332
+ .describe('ISO 8601 UTC instant the applied window starts at: a product issued before this is outside it. Present only under active_only=true.'),
333
+ exclusions: z
334
+ .object({
335
+ agedOut: z
336
+ .number()
337
+ .describe('Issued outside the applied window with no validity end still ahead.'),
338
+ productType: z
339
+ .number()
340
+ .describe('Summaries and unrecognized products, which are never in force.'),
341
+ cancellationRecord: z.number().describe('Cancellation notices themselves.'),
342
+ cancelledBySerial: z
343
+ .number()
344
+ .describe('Named by a later cancellation\'s "Cancel Serial Number:" under the same code.'),
345
+ superseded: z
346
+ .number()
347
+ .describe('Carries the supersede line but is not the newest record that does.'),
348
+ validityElapsed: z
349
+ .number()
350
+ .describe('Validity end already passed, with nothing newer cancelling or superseding it.'),
351
+ })
352
+ .optional()
353
+ .describe('How many products active_only=true left out, by reason. Reasons overlap, so each excluded record is counted under the first that applies, in this field order — these counts plus totalCount equal the number of records the feed carried. Emitted only when something was excluded, and never under active_only=false. Use it to tell "space weather is quiet" from "everything was filtered" without a second active_only=false call.'),
354
+ },
355
+ enrichmentTrailer: {
356
+ // A structured field would otherwise collapse to a JSON blob in the content[]
357
+ // trailer. Reasons that did not fire are dropped and the keys are left as the schema
358
+ // spells them, so a reader can map a count straight back to structuredContent.
359
+ exclusions: {
360
+ render: (value) => {
361
+ const counts = value ?? {};
362
+ const fired = Object.entries(counts)
363
+ .filter(([, count]) => count > 0)
364
+ .map(([reason, count]) => `${count} ${reason}`);
365
+ return `**Excluded:** ${fired.length > 0 ? fired.join(', ') : 'none'}`;
366
+ },
367
+ },
139
368
  },
140
369
  format: (result) => {
141
370
  const lines = [];
142
371
  lines.push(`## SWPC Space Weather Alerts — ${result.fetchedAt}`);
143
- lines.push(`**Total:** ${result.totalCount}`);
372
+ // The scope belongs beside the count: a total of 0 means something different for an
373
+ // in-force query than for an unfiltered one.
374
+ const scope = result.activeOnly
375
+ ? 'active products only'
376
+ : 'all products in the requested window';
377
+ lines.push(`**Total:** ${result.totalCount} · **Scope:** ${scope}`);
144
378
  if (result.alerts.length === 0) {
145
- lines.push('\n_No active alerts._');
379
+ lines.push(result.activeOnly
380
+ ? '\n_No active alerts._'
381
+ : '\n_No space weather products issued in the requested window._');
146
382
  }
147
383
  else {
148
384
  for (const alert of result.alerts) {
@@ -154,7 +390,11 @@ export const getAlerts = tool('noaa_spaceweather_get_alerts', {
154
390
  // Spell out a scale-less product rather than leaving a bare "Level: 0", which
155
391
  // reads as "calm" when it actually means the product states no NOAA scale.
156
392
  const scale = alert.noaaScale ? ` (${alert.noaaScale})` : ' (no NOAA scale)';
157
- lines.push(`**Issued:** ${alert.issueDatetime} | **Level:** ${alert.level}${scale}`);
393
+ // The serial is what makes the "Cancel Serial Number:" and "Continuation of
394
+ // Serial Number:" chains in the body navigable, so it rides here for clients
395
+ // that render only content[].
396
+ const serial = alert.serialNumber ?? 'not stated';
397
+ lines.push(`**Issued:** ${alert.issueDatetime} | **Level:** ${alert.level}${scale} | **Serial:** ${serial}`);
158
398
  if (alert.validFrom)
159
399
  lines.push(`**Valid From:** ${alert.validFrom}`);
160
400
  if (alert.validTo)
@@ -1 +1 @@
1
- {"version":3,"file":"get-alerts.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-alerts.tool.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"get-alerts.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-alerts.tool.ts"],"names":[],"mappings":"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"}
@@ -13,10 +13,12 @@ export declare const getAuroraForecast: import("@cyanheads/mcp-ts-core").ToolDef
13
13
  localLookup: z.ZodNullable<z.ZodObject<{
14
14
  requestedLatitude: z.ZodNumber;
15
15
  requestedLongitude: z.ZodNumber;
16
+ geomagneticLatitude: z.ZodNumber;
16
17
  gridLatitude: z.ZodNumber;
17
18
  gridLongitude: z.ZodNumber;
18
19
  auroraPercent: z.ZodNumber;
19
20
  minKpRequired: z.ZodNumber;
21
+ minGScale: z.ZodNullable<z.ZodNumber>;
20
22
  verdict: z.ZodString;
21
23
  }, z.core.$strip>>;
22
24
  gridPointCount: z.ZodNumber;
@@ -25,9 +27,15 @@ export declare const getAuroraForecast: import("@cyanheads/mcp-ts-core").ToolDef
25
27
  }, z.core.$strip>, readonly [{
26
28
  readonly reason: "feed_unavailable";
27
29
  readonly code: JsonRpcErrorCode.ServiceUnavailable;
28
- readonly when: "SWPC endpoint returns non-OK status or times out after retries.";
30
+ readonly when: "SWPC feed returns 5xx or 429, times out, or answers with a body that is not parseable JSON. Retried before failing.";
29
31
  readonly retryable: true;
30
32
  readonly recovery: "Retry in 30–60 seconds; SWPC feeds occasionally lag during high-activity events.";
33
+ }, {
34
+ readonly reason: "feed_moved";
35
+ readonly code: JsonRpcErrorCode.ServiceUnavailable;
36
+ readonly when: "SWPC feed path returns a permanent 4xx (404, 410, 401, 403). Fails in one attempt.";
37
+ readonly retryable: false;
38
+ readonly recovery: "Retrying will not help — the SWPC feed path no longer resolves or no longer has the expected shape; the feed URL needs updating against SWPC current inventory.";
31
39
  }, {
32
40
  readonly reason: "invalid_coordinates";
33
41
  readonly code: JsonRpcErrorCode.ValidationError;
@@ -1 +1 @@
1
- {"version":3,"file":"get-aurora-forecast.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-aurora-forecast.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAuCjE,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA4L5B,CAAC"}
1
+ {"version":3,"file":"get-aurora-forecast.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-aurora-forecast.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AA0EjE,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA0N5B,CAAC"}