@ryancardin/noaa-tides-currents-mcp-server 1.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/.claude/settings.local.json +29 -0
- package/CLAUDE.md +71 -0
- package/Dockerfile +14 -0
- package/LICENSE +21 -0
- package/README.md +234 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -0
- package/dist/interfaces/config.d.ts +6 -0
- package/dist/interfaces/config.js +1 -0
- package/dist/interfaces/moon.d.ts +65 -0
- package/dist/interfaces/moon.js +35 -0
- package/dist/interfaces/noaa.d.ts +334 -0
- package/dist/interfaces/noaa.js +98 -0
- package/dist/interfaces/parameters.d.ts +18 -0
- package/dist/interfaces/parameters.js +5 -0
- package/dist/interfaces/sun.d.ts +103 -0
- package/dist/interfaces/sun.js +45 -0
- package/dist/mcp-server.d.ts +12 -0
- package/dist/mcp-server.js +103 -0
- package/dist/moon-phase-service.d.ts +122 -0
- package/dist/moon-phase-service.js +187 -0
- package/dist/noaa-service.d.ts +60 -0
- package/dist/noaa-service.js +159 -0
- package/dist/schemas/common.d.ts +14 -0
- package/dist/schemas/common.js +18 -0
- package/dist/schemas/dpapi.d.ts +198 -0
- package/dist/schemas/dpapi.js +89 -0
- package/dist/server/config.d.ts +9 -0
- package/dist/server/config.js +40 -0
- package/dist/server/mcp-server.d.ts +12 -0
- package/dist/server/mcp-server.js +103 -0
- package/dist/services/dpapi-service.d.ts +72 -0
- package/dist/services/dpapi-service.js +164 -0
- package/dist/services/moon-phase-service.d.ts +34 -0
- package/dist/services/moon-phase-service.js +147 -0
- package/dist/services/noaa-parameters-service.d.ts +76 -0
- package/dist/services/noaa-parameters-service.js +128 -0
- package/dist/services/noaa-service.d.ts +52 -0
- package/dist/services/noaa-service.js +151 -0
- package/dist/services/sun-service.d.ts +60 -0
- package/dist/services/sun-service.js +234 -0
- package/dist/sun-service.d.ts +184 -0
- package/dist/sun-service.js +218 -0
- package/dist/tools/derived-product-tools.d.ts +6 -0
- package/dist/tools/derived-product-tools.js +168 -0
- package/dist/tools/index.d.ts +16 -0
- package/dist/tools/index.js +36 -0
- package/dist/tools/moon-tools.d.ts +6 -0
- package/dist/tools/moon-tools.js +69 -0
- package/dist/tools/parameter-tools.d.ts +6 -0
- package/dist/tools/parameter-tools.js +77 -0
- package/dist/tools/station-tools.d.ts +6 -0
- package/dist/tools/station-tools.js +51 -0
- package/dist/tools/sun-tools.d.ts +6 -0
- package/dist/tools/sun-tools.js +109 -0
- package/dist/tools/water-tools.d.ts +6 -0
- package/dist/tools/water-tools.js +150 -0
- package/dist/types/moon.d.ts +26 -0
- package/dist/types/moon.js +14 -0
- package/dist/types/sun.d.ts +49 -0
- package/dist/types/sun.js +19 -0
- package/dist/types.d.ts +337 -0
- package/dist/types.js +98 -0
- package/package.json +56 -0
- package/smithery.yaml +16 -0
- package/src/index.ts +13 -0
- package/src/interfaces/moon.ts +44 -0
- package/src/interfaces/noaa.ts +130 -0
- package/src/interfaces/parameters.ts +20 -0
- package/src/interfaces/sun.ts +57 -0
- package/src/schemas/common.ts +23 -0
- package/src/schemas/dpapi.ts +99 -0
- package/src/server/config.ts +43 -0
- package/src/server/mcp-server.ts +135 -0
- package/src/services/dpapi-service.ts +187 -0
- package/src/services/moon-phase-service.ts +167 -0
- package/src/services/noaa-parameters-service.ts +139 -0
- package/src/services/noaa-service.ts +171 -0
- package/src/services/sun-service.ts +275 -0
- package/src/tools/derived-product-tools.ts +180 -0
- package/src/tools/index.ts +40 -0
- package/src/tools/moon-tools.ts +79 -0
- package/src/tools/parameter-tools.ts +82 -0
- package/src/tools/station-tools.ts +57 -0
- package/src/tools/sun-tools.ts +120 -0
- package/src/tools/water-tools.ts +166 -0
- package/src/types/moon.ts +27 -0
- package/src/types/sun.ts +51 -0
- package/src/types/suncalc.d.ts +110 -0
- package/test-dpapi.js +0 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import SunCalc from 'suncalc';
|
|
2
|
+
import { SunTimesParams, SunTimesRangeParams, SunPositionParams, NextSunEventParams } from '../interfaces/sun.js';
|
|
3
|
+
import { SunTimesInfo, SunPositionInfo, SunEventType } from '../types/sun.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Service for sun calculations
|
|
7
|
+
*/
|
|
8
|
+
export class SunService {
|
|
9
|
+
/**
|
|
10
|
+
* Get sun times for a specific date and location
|
|
11
|
+
* @param params Parameters for the request
|
|
12
|
+
* @returns Sun times information
|
|
13
|
+
*/
|
|
14
|
+
getSunTimes(params: SunTimesParams): SunTimesInfo {
|
|
15
|
+
const date = params.date ? new Date(params.date) : new Date();
|
|
16
|
+
const { latitude, longitude } = params;
|
|
17
|
+
|
|
18
|
+
// Get sun times data
|
|
19
|
+
const sunTimes = SunCalc.getTimes(date, latitude, longitude);
|
|
20
|
+
|
|
21
|
+
// Format times or return null if not available
|
|
22
|
+
const formatTime = (time: Date | null): string | null => {
|
|
23
|
+
if (!time || isNaN(time.getTime())) return null;
|
|
24
|
+
|
|
25
|
+
if (params.timezone) {
|
|
26
|
+
try {
|
|
27
|
+
return time.toLocaleTimeString('en-US', { timeZone: params.timezone });
|
|
28
|
+
} catch (error) {
|
|
29
|
+
// If timezone is invalid, fall back to ISO string
|
|
30
|
+
console.warn(`Invalid timezone: ${params.timezone}. Using UTC.`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return time.toISOString();
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Calculate day length in minutes
|
|
38
|
+
const sunrise = sunTimes.sunrise;
|
|
39
|
+
const sunset = sunTimes.sunset;
|
|
40
|
+
let dayLength = 0;
|
|
41
|
+
|
|
42
|
+
if (sunrise && sunset && !isNaN(sunrise.getTime()) && !isNaN(sunset.getTime())) {
|
|
43
|
+
dayLength = (sunset.getTime() - sunrise.getTime()) / (60 * 1000);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
date: date.toISOString().split('T')[0],
|
|
48
|
+
sunrise: formatTime(sunTimes.sunrise),
|
|
49
|
+
sunset: formatTime(sunTimes.sunset),
|
|
50
|
+
solarNoon: formatTime(sunTimes.solarNoon),
|
|
51
|
+
dawn: formatTime(sunTimes.dawn),
|
|
52
|
+
dusk: formatTime(sunTimes.dusk),
|
|
53
|
+
nightStart: formatTime(sunTimes.night),
|
|
54
|
+
nightEnd: formatTime(sunTimes.nightEnd),
|
|
55
|
+
goldenHourStart: formatTime(sunTimes.goldenHour),
|
|
56
|
+
goldenHourEnd: formatTime(sunTimes.goldenHourEnd),
|
|
57
|
+
nauticalDawn: formatTime(sunTimes.nauticalDawn),
|
|
58
|
+
nauticalDusk: formatTime(sunTimes.nauticalDusk),
|
|
59
|
+
astronomicalDawn: formatTime(sunTimes.astronomicalDawn),
|
|
60
|
+
astronomicalDusk: formatTime(sunTimes.astronomicalDusk),
|
|
61
|
+
dayLength
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get sun times for a date range
|
|
67
|
+
* @param params Parameters for the request
|
|
68
|
+
* @returns Array of sun times information
|
|
69
|
+
*/
|
|
70
|
+
getSunTimesRange(params: SunTimesRangeParams): SunTimesInfo[] {
|
|
71
|
+
const startDate = new Date(params.start_date);
|
|
72
|
+
const endDate = new Date(params.end_date);
|
|
73
|
+
|
|
74
|
+
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
|
|
75
|
+
throw new Error('Invalid date format. Please use YYYY-MM-DD format.');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (startDate > endDate) {
|
|
79
|
+
throw new Error('Start date must be before end date.');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const result: SunTimesInfo[] = [];
|
|
83
|
+
const currentDate = new Date(startDate);
|
|
84
|
+
|
|
85
|
+
while (currentDate <= endDate) {
|
|
86
|
+
result.push(this.getSunTimes({
|
|
87
|
+
date: currentDate.toISOString().split('T')[0],
|
|
88
|
+
latitude: params.latitude,
|
|
89
|
+
longitude: params.longitude,
|
|
90
|
+
timezone: params.timezone
|
|
91
|
+
}));
|
|
92
|
+
|
|
93
|
+
// Move to next day
|
|
94
|
+
currentDate.setDate(currentDate.getDate() + 1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get sun position for a specific date, time, and location
|
|
102
|
+
* @param params Parameters for the request
|
|
103
|
+
* @returns Sun position information
|
|
104
|
+
*/
|
|
105
|
+
getSunPosition(params: SunPositionParams): SunPositionInfo {
|
|
106
|
+
const date = params.date ? new Date(params.date) : new Date();
|
|
107
|
+
const time = params.time;
|
|
108
|
+
const { latitude, longitude } = params;
|
|
109
|
+
|
|
110
|
+
// Set the time if provided
|
|
111
|
+
if (time) {
|
|
112
|
+
const [hours, minutes, seconds] = time.split(':').map(Number);
|
|
113
|
+
|
|
114
|
+
if (!isNaN(hours) && !isNaN(minutes) && (!seconds || !isNaN(seconds))) {
|
|
115
|
+
date.setHours(hours, minutes, seconds || 0, 0);
|
|
116
|
+
} else {
|
|
117
|
+
throw new Error('Invalid time format. Please use HH:MM:SS format.');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Get sun position data
|
|
122
|
+
const position = SunCalc.getPosition(date, latitude, longitude);
|
|
123
|
+
|
|
124
|
+
// Calculate right ascension and declination (approximate values)
|
|
125
|
+
// Note: These are approximate calculations and may not be precise
|
|
126
|
+
const equatorialCoords = this.calculateEquatorialCoordinates(date, position.azimuth, position.altitude, latitude, longitude);
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
date: date.toISOString().split('T')[0],
|
|
130
|
+
time: date.toISOString().split('T')[1].split('.')[0],
|
|
131
|
+
azimuth: position.azimuth * (180 / Math.PI),
|
|
132
|
+
altitude: position.altitude * (180 / Math.PI),
|
|
133
|
+
declination: equatorialCoords.declination,
|
|
134
|
+
rightAscension: equatorialCoords.rightAscension
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get the next occurrence(s) of a specific sun event
|
|
140
|
+
* @param params Parameters for the request
|
|
141
|
+
* @returns Array of dates for the next occurrences of the specified event
|
|
142
|
+
*/
|
|
143
|
+
getNextSunEvent(params: NextSunEventParams): { date: string, time: string, event: string }[] {
|
|
144
|
+
const startDate = params.date ? new Date(params.date) : new Date();
|
|
145
|
+
const count = params.count !== undefined ? params.count : 1;
|
|
146
|
+
const { latitude, longitude } = params;
|
|
147
|
+
const timezone = params.timezone !== undefined ? params.timezone : 'UTC';
|
|
148
|
+
|
|
149
|
+
const results: { date: string, time: string, event: string }[] = [];
|
|
150
|
+
let currentDate = new Date(startDate);
|
|
151
|
+
|
|
152
|
+
// Find the next occurrences
|
|
153
|
+
while (results.length < count) {
|
|
154
|
+
const sunTimes = SunCalc.getTimes(currentDate, latitude, longitude);
|
|
155
|
+
const eventTime = sunTimes[params.event as keyof typeof sunTimes];
|
|
156
|
+
|
|
157
|
+
if (eventTime && !isNaN(eventTime.getTime()) && eventTime > startDate) {
|
|
158
|
+
let formattedTime: string;
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
formattedTime = eventTime.toLocaleTimeString('en-US', { timeZone: timezone });
|
|
162
|
+
} catch (error) {
|
|
163
|
+
// If timezone is invalid, fall back to ISO string
|
|
164
|
+
console.warn(`Invalid timezone: ${timezone}. Using UTC.`);
|
|
165
|
+
formattedTime = eventTime.toISOString().split('T')[1].split('.')[0];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
results.push({
|
|
169
|
+
date: eventTime.toISOString().split('T')[0],
|
|
170
|
+
time: formattedTime,
|
|
171
|
+
event: params.event as string
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Move to next day to find the next occurrence
|
|
175
|
+
currentDate.setDate(currentDate.getDate() + 1);
|
|
176
|
+
} else {
|
|
177
|
+
// Event not found for this day, try next day
|
|
178
|
+
currentDate.setDate(currentDate.getDate() + 1);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Safety check to prevent infinite loops
|
|
182
|
+
if (results.length === 0 && currentDate.getTime() - startDate.getTime() > 366 * 24 * 60 * 60 * 1000) {
|
|
183
|
+
throw new Error('Could not find the specified sun event within a year.');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return results;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Calculate approximate equatorial coordinates (right ascension and declination)
|
|
192
|
+
* from horizontal coordinates (azimuth and altitude)
|
|
193
|
+
* Note: This is a simplified calculation and may not be precise
|
|
194
|
+
* @param date Date of observation
|
|
195
|
+
* @param azimuth Azimuth in radians
|
|
196
|
+
* @param altitude Altitude in radians
|
|
197
|
+
* @param latitude Observer's latitude
|
|
198
|
+
* @param longitude Observer's longitude
|
|
199
|
+
* @returns Approximate equatorial coordinates
|
|
200
|
+
*/
|
|
201
|
+
private calculateEquatorialCoordinates(date: Date, azimuth: number, altitude: number, latitude: number, longitude: number): { rightAscension: number, declination: number } {
|
|
202
|
+
// Convert degrees to radians
|
|
203
|
+
const lat = latitude * (Math.PI / 180);
|
|
204
|
+
|
|
205
|
+
// Calculate hour angle and declination
|
|
206
|
+
const sinDec = Math.sin(altitude) * Math.sin(lat) + Math.cos(altitude) * Math.cos(lat) * Math.cos(azimuth);
|
|
207
|
+
const declination = Math.asin(sinDec) * (180 / Math.PI);
|
|
208
|
+
|
|
209
|
+
const cosH = (Math.sin(altitude) - Math.sin(lat) * sinDec) / (Math.cos(lat) * Math.cos(declination * (Math.PI / 180)));
|
|
210
|
+
const hourAngle = Math.acos(Math.max(-1, Math.min(1, cosH)));
|
|
211
|
+
|
|
212
|
+
// Adjust hour angle based on azimuth
|
|
213
|
+
const adjustedHourAngle = (azimuth > 0 && azimuth < Math.PI) ? (2 * Math.PI - hourAngle) : hourAngle;
|
|
214
|
+
|
|
215
|
+
// Calculate right ascension
|
|
216
|
+
const localSiderealTime = this.calculateLocalSiderealTime(date, longitude);
|
|
217
|
+
let rightAscension = (localSiderealTime - adjustedHourAngle) * (12 / Math.PI);
|
|
218
|
+
|
|
219
|
+
// Normalize right ascension to 0-24 hours
|
|
220
|
+
rightAscension = rightAscension % 24;
|
|
221
|
+
if (rightAscension < 0) rightAscension += 24;
|
|
222
|
+
|
|
223
|
+
return { rightAscension, declination };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Calculate approximate local sidereal time
|
|
228
|
+
* @param date Date of observation
|
|
229
|
+
* @param longitude Observer's longitude
|
|
230
|
+
* @returns Local sidereal time in radians
|
|
231
|
+
*/
|
|
232
|
+
private calculateLocalSiderealTime(date: Date, longitude: number): number {
|
|
233
|
+
// Calculate days since J2000.0
|
|
234
|
+
const jd = this.calculateJulianDay(date);
|
|
235
|
+
const d = jd - 2451545.0;
|
|
236
|
+
|
|
237
|
+
// Calculate Greenwich Mean Sidereal Time
|
|
238
|
+
const gmst = (18.697374558 + 24.06570982441908 * d) % 24;
|
|
239
|
+
|
|
240
|
+
// Convert longitude to hours and calculate local sidereal time
|
|
241
|
+
const longitudeHours = longitude / 15;
|
|
242
|
+
let lst = gmst + longitudeHours;
|
|
243
|
+
|
|
244
|
+
// Normalize to 0-24 hours
|
|
245
|
+
lst = lst % 24;
|
|
246
|
+
if (lst < 0) lst += 24;
|
|
247
|
+
|
|
248
|
+
// Convert to radians
|
|
249
|
+
return lst * (Math.PI / 12);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Calculate Julian day from date
|
|
254
|
+
* @param date Date to convert
|
|
255
|
+
* @returns Julian day
|
|
256
|
+
*/
|
|
257
|
+
private calculateJulianDay(date: Date): number {
|
|
258
|
+
const y = date.getFullYear();
|
|
259
|
+
const m = date.getMonth() + 1;
|
|
260
|
+
const d = date.getDate();
|
|
261
|
+
|
|
262
|
+
// Calculate Julian day
|
|
263
|
+
const jd = 367 * y - Math.floor(7 * (y + Math.floor((m + 9) / 12)) / 4) -
|
|
264
|
+
Math.floor(3 * (Math.floor((y + (m - 9) / 7) / 100) + 1) / 4) +
|
|
265
|
+
Math.floor(275 * m / 9) + d + 1721028.5;
|
|
266
|
+
|
|
267
|
+
// Add time of day
|
|
268
|
+
const hours = date.getUTCHours();
|
|
269
|
+
const minutes = date.getUTCMinutes();
|
|
270
|
+
const seconds = date.getUTCSeconds();
|
|
271
|
+
const milliseconds = date.getUTCMilliseconds();
|
|
272
|
+
|
|
273
|
+
return jd + (hours + minutes / 60 + seconds / 3600 + milliseconds / 3600000) / 24;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { FastMCP } from 'fastmcp';
|
|
2
|
+
import { DpapiService } from '../services/dpapi-service.js';
|
|
3
|
+
import {
|
|
4
|
+
SeaLevelTrendsSchema,
|
|
5
|
+
ExtremeWaterLevelsSchema,
|
|
6
|
+
HighTideFloodingDailySchema,
|
|
7
|
+
HighTideFloodingMonthlySchema,
|
|
8
|
+
HighTideFloodingSeasonalSchema,
|
|
9
|
+
HighTideFloodingAnnualSchema,
|
|
10
|
+
HighTideFloodingProjectionsSchema,
|
|
11
|
+
HighTideFloodingLikelihoodsSchema,
|
|
12
|
+
TopTenWaterLevelsSchema
|
|
13
|
+
} from '../schemas/dpapi.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Register derived product tools with the MCP server
|
|
17
|
+
*/
|
|
18
|
+
export function registerDerivedProductTools(server: FastMCP, dpapiService: DpapiService) {
|
|
19
|
+
// Sea Level Trends tool
|
|
20
|
+
server.addTool({
|
|
21
|
+
name: 'get_sea_level_trends',
|
|
22
|
+
description: 'Get sea level trends and error margins for a station',
|
|
23
|
+
parameters: SeaLevelTrendsSchema,
|
|
24
|
+
execute: async (params) => {
|
|
25
|
+
try {
|
|
26
|
+
const result = await dpapiService.getSeaLevelTrends(params);
|
|
27
|
+
return JSON.stringify(result);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error instanceof Error) {
|
|
30
|
+
throw new Error(`Failed to get sea level trends: ${error.message}`);
|
|
31
|
+
}
|
|
32
|
+
throw new Error('Failed to get sea level trends');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Extreme Water Levels tool
|
|
38
|
+
server.addTool({
|
|
39
|
+
name: 'get_extreme_water_levels',
|
|
40
|
+
description: 'Get extreme water levels and exceedance probabilities for a station',
|
|
41
|
+
parameters: ExtremeWaterLevelsSchema,
|
|
42
|
+
execute: async (params) => {
|
|
43
|
+
try {
|
|
44
|
+
const result = await dpapiService.getExtremeWaterLevels(params);
|
|
45
|
+
return JSON.stringify(result);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (error instanceof Error) {
|
|
48
|
+
throw new Error(`Failed to get extreme water levels: ${error.message}`);
|
|
49
|
+
}
|
|
50
|
+
throw new Error('Failed to get extreme water levels');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// High Tide Flooding Daily tool
|
|
56
|
+
server.addTool({
|
|
57
|
+
name: 'get_high_tide_flooding_daily',
|
|
58
|
+
description: 'Get high tide flooding daily count data for a station',
|
|
59
|
+
parameters: HighTideFloodingDailySchema,
|
|
60
|
+
execute: async (params) => {
|
|
61
|
+
try {
|
|
62
|
+
const result = await dpapiService.getHighTideFloodingDaily(params);
|
|
63
|
+
return JSON.stringify(result);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error instanceof Error) {
|
|
66
|
+
throw new Error(`Failed to get high tide flooding daily data: ${error.message}`);
|
|
67
|
+
}
|
|
68
|
+
throw new Error('Failed to get high tide flooding daily data');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// High Tide Flooding Monthly tool
|
|
74
|
+
server.addTool({
|
|
75
|
+
name: 'get_high_tide_flooding_monthly',
|
|
76
|
+
description: 'Get high tide flooding monthly count data for a station',
|
|
77
|
+
parameters: HighTideFloodingMonthlySchema,
|
|
78
|
+
execute: async (params) => {
|
|
79
|
+
try {
|
|
80
|
+
const result = await dpapiService.getHighTideFloodingMonthly(params);
|
|
81
|
+
return JSON.stringify(result);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (error instanceof Error) {
|
|
84
|
+
throw new Error(`Failed to get high tide flooding monthly data: ${error.message}`);
|
|
85
|
+
}
|
|
86
|
+
throw new Error('Failed to get high tide flooding monthly data');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// High Tide Flooding Seasonal tool
|
|
92
|
+
server.addTool({
|
|
93
|
+
name: 'get_high_tide_flooding_seasonal',
|
|
94
|
+
description: 'Get high tide flooding seasonal count data for a station',
|
|
95
|
+
parameters: HighTideFloodingSeasonalSchema,
|
|
96
|
+
execute: async (params) => {
|
|
97
|
+
try {
|
|
98
|
+
const result = await dpapiService.getHighTideFloodingSeasonal(params);
|
|
99
|
+
return JSON.stringify(result);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (error instanceof Error) {
|
|
102
|
+
throw new Error(`Failed to get high tide flooding seasonal data: ${error.message}`);
|
|
103
|
+
}
|
|
104
|
+
throw new Error('Failed to get high tide flooding seasonal data');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// High Tide Flooding Annual tool
|
|
110
|
+
server.addTool({
|
|
111
|
+
name: 'get_high_tide_flooding_annual',
|
|
112
|
+
description: 'Get high tide flooding annual count data for a station',
|
|
113
|
+
parameters: HighTideFloodingAnnualSchema,
|
|
114
|
+
execute: async (params) => {
|
|
115
|
+
try {
|
|
116
|
+
const result = await dpapiService.getHighTideFloodingAnnual(params);
|
|
117
|
+
return JSON.stringify(result);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (error instanceof Error) {
|
|
120
|
+
throw new Error(`Failed to get high tide flooding annual data: ${error.message}`);
|
|
121
|
+
}
|
|
122
|
+
throw new Error('Failed to get high tide flooding annual data');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// High Tide Flooding Projections tool
|
|
128
|
+
server.addTool({
|
|
129
|
+
name: 'get_high_tide_flooding_projections',
|
|
130
|
+
description: 'Get high tide flooding decadal projections for sea level rise scenarios',
|
|
131
|
+
parameters: HighTideFloodingProjectionsSchema,
|
|
132
|
+
execute: async (params) => {
|
|
133
|
+
try {
|
|
134
|
+
const result = await dpapiService.getHighTideFloodingProjections(params);
|
|
135
|
+
return JSON.stringify(result);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (error instanceof Error) {
|
|
138
|
+
throw new Error(`Failed to get high tide flooding projections: ${error.message}`);
|
|
139
|
+
}
|
|
140
|
+
throw new Error('Failed to get high tide flooding projections');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// High Tide Flooding Likelihoods tool
|
|
146
|
+
server.addTool({
|
|
147
|
+
name: 'get_high_tide_flooding_likelihoods',
|
|
148
|
+
description: 'Get high tide flooding daily likelihoods for a station',
|
|
149
|
+
parameters: HighTideFloodingLikelihoodsSchema,
|
|
150
|
+
execute: async (params) => {
|
|
151
|
+
try {
|
|
152
|
+
const result = await dpapiService.getHighTideFloodingLikelihoods(params);
|
|
153
|
+
return JSON.stringify(result);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
if (error instanceof Error) {
|
|
156
|
+
throw new Error(`Failed to get high tide flooding likelihoods: ${error.message}`);
|
|
157
|
+
}
|
|
158
|
+
throw new Error('Failed to get high tide flooding likelihoods');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Top Ten Water Levels tool
|
|
164
|
+
server.addTool({
|
|
165
|
+
name: 'get_top_ten_water_levels',
|
|
166
|
+
description: 'Get top ten highest or lowest water levels for a station',
|
|
167
|
+
parameters: TopTenWaterLevelsSchema,
|
|
168
|
+
execute: async (params) => {
|
|
169
|
+
try {
|
|
170
|
+
const result = await dpapiService.getTopTenWaterLevels(params);
|
|
171
|
+
return JSON.stringify(result);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (error instanceof Error) {
|
|
174
|
+
throw new Error(`Failed to get top ten water levels: ${error.message}`);
|
|
175
|
+
}
|
|
176
|
+
throw new Error('Failed to get top ten water levels');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { FastMCP } from 'fastmcp';
|
|
2
|
+
import { NoaaService } from '../services/noaa-service.js';
|
|
3
|
+
import { MoonPhaseService } from '../services/moon-phase-service.js';
|
|
4
|
+
import { SunService } from '../services/sun-service.js';
|
|
5
|
+
import { NoaaParametersService } from '../services/noaa-parameters-service.js';
|
|
6
|
+
import { DpapiService } from '../services/dpapi-service.js';
|
|
7
|
+
import { registerWaterTools } from './water-tools.js';
|
|
8
|
+
import { registerStationTools } from './station-tools.js';
|
|
9
|
+
import { registerMoonTools } from './moon-tools.js';
|
|
10
|
+
import { registerSunTools } from './sun-tools.js';
|
|
11
|
+
import { registerParameterTools } from './parameter-tools.js';
|
|
12
|
+
import { registerDerivedProductTools } from './derived-product-tools.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Register all tools with the MCP server
|
|
16
|
+
*/
|
|
17
|
+
export function registerAllTools(server: FastMCP) {
|
|
18
|
+
// Create service instances
|
|
19
|
+
const noaaService = new NoaaService();
|
|
20
|
+
const moonPhaseService = new MoonPhaseService();
|
|
21
|
+
const sunService = new SunService();
|
|
22
|
+
const parametersService = new NoaaParametersService();
|
|
23
|
+
const dpapiService = new DpapiService();
|
|
24
|
+
|
|
25
|
+
// Register tools by category
|
|
26
|
+
registerWaterTools(server, noaaService);
|
|
27
|
+
registerStationTools(server, noaaService);
|
|
28
|
+
registerMoonTools(server, moonPhaseService);
|
|
29
|
+
registerSunTools(server, sunService);
|
|
30
|
+
registerParameterTools(server, parametersService);
|
|
31
|
+
registerDerivedProductTools(server, dpapiService);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
noaaService,
|
|
35
|
+
moonPhaseService,
|
|
36
|
+
sunService,
|
|
37
|
+
parametersService,
|
|
38
|
+
dpapiService
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { FastMCP } from 'fastmcp';
|
|
2
|
+
import { MoonPhaseService } from '../services/moon-phase-service.js';
|
|
3
|
+
import {
|
|
4
|
+
MoonPhaseParamsSchema,
|
|
5
|
+
MoonPhasesRangeParamsSchema,
|
|
6
|
+
NextMoonPhaseParamsSchema
|
|
7
|
+
} from '../interfaces/moon.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Register moon-related tools with the MCP server
|
|
11
|
+
*/
|
|
12
|
+
export function registerMoonTools(server: FastMCP, moonPhaseService: MoonPhaseService) {
|
|
13
|
+
// Add moon phase tool
|
|
14
|
+
server.addTool({
|
|
15
|
+
name: 'get_moon_phase',
|
|
16
|
+
description: 'Get moon phase information for a specific date',
|
|
17
|
+
parameters: MoonPhaseParamsSchema,
|
|
18
|
+
execute: async (params) => {
|
|
19
|
+
try {
|
|
20
|
+
const result = moonPhaseService.getMoonPhase(params);
|
|
21
|
+
if (params.format === 'text') {
|
|
22
|
+
return `Moon phase for ${result.date}: ${result.phaseName} (${(result.illumination * 100).toFixed(1)}% illuminated)`;
|
|
23
|
+
}
|
|
24
|
+
return JSON.stringify(result);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (error instanceof Error) {
|
|
27
|
+
throw new Error(`Failed to get moon phase: ${error.message}`);
|
|
28
|
+
}
|
|
29
|
+
throw new Error('Failed to get moon phase');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Add moon phases range tool
|
|
35
|
+
server.addTool({
|
|
36
|
+
name: 'get_moon_phases_range',
|
|
37
|
+
description: 'Get moon phase information for a date range',
|
|
38
|
+
parameters: MoonPhasesRangeParamsSchema,
|
|
39
|
+
execute: async (params) => {
|
|
40
|
+
try {
|
|
41
|
+
const results = moonPhaseService.getMoonPhasesRange(params);
|
|
42
|
+
if (params.format === 'text') {
|
|
43
|
+
return results.map(result =>
|
|
44
|
+
`${result.date}: ${result.phaseName} (${(result.illumination * 100).toFixed(1)}% illuminated)`
|
|
45
|
+
).join('\n');
|
|
46
|
+
}
|
|
47
|
+
return JSON.stringify(results);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if (error instanceof Error) {
|
|
50
|
+
throw new Error(`Failed to get moon phases: ${error.message}`);
|
|
51
|
+
}
|
|
52
|
+
throw new Error('Failed to get moon phases');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Add next moon phase tool
|
|
58
|
+
server.addTool({
|
|
59
|
+
name: 'get_next_moon_phase',
|
|
60
|
+
description: 'Get the next occurrence(s) of a specific moon phase',
|
|
61
|
+
parameters: NextMoonPhaseParamsSchema,
|
|
62
|
+
execute: async (params) => {
|
|
63
|
+
try {
|
|
64
|
+
const results = moonPhaseService.getNextMoonPhase(params);
|
|
65
|
+
if (params.format === 'text') {
|
|
66
|
+
return results.map(result =>
|
|
67
|
+
`Next ${result.phase}: ${result.date}`
|
|
68
|
+
).join('\n');
|
|
69
|
+
}
|
|
70
|
+
return JSON.stringify(results);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (error instanceof Error) {
|
|
73
|
+
throw new Error(`Failed to get next moon phase: ${error.message}`);
|
|
74
|
+
}
|
|
75
|
+
throw new Error('Failed to get next moon phase');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { FastMCP } from 'fastmcp';
|
|
2
|
+
import { ParameterSchema } from '../interfaces/parameters.js';
|
|
3
|
+
import { NoaaParametersService } from '../services/noaa-parameters-service.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Register parameter tools with the MCP server
|
|
7
|
+
*/
|
|
8
|
+
export function registerParameterTools(server: FastMCP, parametersService: NoaaParametersService) {
|
|
9
|
+
/**
|
|
10
|
+
* Add parameter definitions tool
|
|
11
|
+
*/
|
|
12
|
+
server.addTool({
|
|
13
|
+
name: 'get_parameter_definitions',
|
|
14
|
+
description: 'Get information about valid parameter values for NOAA API requests',
|
|
15
|
+
parameters: ParameterSchema,
|
|
16
|
+
execute: async (params) => {
|
|
17
|
+
try {
|
|
18
|
+
const { parameter } = params;
|
|
19
|
+
|
|
20
|
+
// If no parameter specified, return all parameter types and their descriptions
|
|
21
|
+
if (!parameter) {
|
|
22
|
+
return JSON.stringify({
|
|
23
|
+
time_zones: parametersService.getTimeZones(),
|
|
24
|
+
datums: parametersService.getDatums(),
|
|
25
|
+
units: parametersService.getUnits(),
|
|
26
|
+
tide_intervals: parametersService.getTidePredictionIntervals(),
|
|
27
|
+
current_intervals: parametersService.getCurrentPredictionIntervals(),
|
|
28
|
+
velocity_types: parametersService.getVelocityTypes(),
|
|
29
|
+
products: parametersService.getMeteorologicalProducts(),
|
|
30
|
+
station_types: parametersService.getStationTypes(),
|
|
31
|
+
date_formats: parametersService.getDateFormats(),
|
|
32
|
+
output_formats: parametersService.getOutputFormats()
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Return specific parameter information based on the parameter type
|
|
37
|
+
let result;
|
|
38
|
+
switch (parameter) {
|
|
39
|
+
case 'time_zones':
|
|
40
|
+
result = parametersService.getTimeZones();
|
|
41
|
+
break;
|
|
42
|
+
case 'datums':
|
|
43
|
+
result = parametersService.getDatums();
|
|
44
|
+
break;
|
|
45
|
+
case 'units':
|
|
46
|
+
result = parametersService.getUnits();
|
|
47
|
+
break;
|
|
48
|
+
case 'tide_intervals':
|
|
49
|
+
result = parametersService.getTidePredictionIntervals();
|
|
50
|
+
break;
|
|
51
|
+
case 'current_intervals':
|
|
52
|
+
result = parametersService.getCurrentPredictionIntervals();
|
|
53
|
+
break;
|
|
54
|
+
case 'velocity_types':
|
|
55
|
+
result = parametersService.getVelocityTypes();
|
|
56
|
+
break;
|
|
57
|
+
case 'products':
|
|
58
|
+
result = parametersService.getMeteorologicalProducts();
|
|
59
|
+
break;
|
|
60
|
+
case 'station_types':
|
|
61
|
+
result = parametersService.getStationTypes();
|
|
62
|
+
break;
|
|
63
|
+
case 'date_formats':
|
|
64
|
+
result = parametersService.getDateFormats();
|
|
65
|
+
break;
|
|
66
|
+
case 'output_formats':
|
|
67
|
+
result = parametersService.getOutputFormats();
|
|
68
|
+
break;
|
|
69
|
+
default:
|
|
70
|
+
throw new Error(`Unknown parameter type: ${parameter}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return JSON.stringify(result);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (error instanceof Error) {
|
|
76
|
+
throw new Error(`Failed to get parameter definitions: ${error.message}`);
|
|
77
|
+
}
|
|
78
|
+
throw new Error('Failed to get parameter definitions');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|