@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.
Files changed (91) hide show
  1. package/.claude/settings.local.json +29 -0
  2. package/CLAUDE.md +71 -0
  3. package/Dockerfile +14 -0
  4. package/LICENSE +21 -0
  5. package/README.md +234 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.js +9 -0
  8. package/dist/interfaces/config.d.ts +6 -0
  9. package/dist/interfaces/config.js +1 -0
  10. package/dist/interfaces/moon.d.ts +65 -0
  11. package/dist/interfaces/moon.js +35 -0
  12. package/dist/interfaces/noaa.d.ts +334 -0
  13. package/dist/interfaces/noaa.js +98 -0
  14. package/dist/interfaces/parameters.d.ts +18 -0
  15. package/dist/interfaces/parameters.js +5 -0
  16. package/dist/interfaces/sun.d.ts +103 -0
  17. package/dist/interfaces/sun.js +45 -0
  18. package/dist/mcp-server.d.ts +12 -0
  19. package/dist/mcp-server.js +103 -0
  20. package/dist/moon-phase-service.d.ts +122 -0
  21. package/dist/moon-phase-service.js +187 -0
  22. package/dist/noaa-service.d.ts +60 -0
  23. package/dist/noaa-service.js +159 -0
  24. package/dist/schemas/common.d.ts +14 -0
  25. package/dist/schemas/common.js +18 -0
  26. package/dist/schemas/dpapi.d.ts +198 -0
  27. package/dist/schemas/dpapi.js +89 -0
  28. package/dist/server/config.d.ts +9 -0
  29. package/dist/server/config.js +40 -0
  30. package/dist/server/mcp-server.d.ts +12 -0
  31. package/dist/server/mcp-server.js +103 -0
  32. package/dist/services/dpapi-service.d.ts +72 -0
  33. package/dist/services/dpapi-service.js +164 -0
  34. package/dist/services/moon-phase-service.d.ts +34 -0
  35. package/dist/services/moon-phase-service.js +147 -0
  36. package/dist/services/noaa-parameters-service.d.ts +76 -0
  37. package/dist/services/noaa-parameters-service.js +128 -0
  38. package/dist/services/noaa-service.d.ts +52 -0
  39. package/dist/services/noaa-service.js +151 -0
  40. package/dist/services/sun-service.d.ts +60 -0
  41. package/dist/services/sun-service.js +234 -0
  42. package/dist/sun-service.d.ts +184 -0
  43. package/dist/sun-service.js +218 -0
  44. package/dist/tools/derived-product-tools.d.ts +6 -0
  45. package/dist/tools/derived-product-tools.js +168 -0
  46. package/dist/tools/index.d.ts +16 -0
  47. package/dist/tools/index.js +36 -0
  48. package/dist/tools/moon-tools.d.ts +6 -0
  49. package/dist/tools/moon-tools.js +69 -0
  50. package/dist/tools/parameter-tools.d.ts +6 -0
  51. package/dist/tools/parameter-tools.js +77 -0
  52. package/dist/tools/station-tools.d.ts +6 -0
  53. package/dist/tools/station-tools.js +51 -0
  54. package/dist/tools/sun-tools.d.ts +6 -0
  55. package/dist/tools/sun-tools.js +109 -0
  56. package/dist/tools/water-tools.d.ts +6 -0
  57. package/dist/tools/water-tools.js +150 -0
  58. package/dist/types/moon.d.ts +26 -0
  59. package/dist/types/moon.js +14 -0
  60. package/dist/types/sun.d.ts +49 -0
  61. package/dist/types/sun.js +19 -0
  62. package/dist/types.d.ts +337 -0
  63. package/dist/types.js +98 -0
  64. package/package.json +56 -0
  65. package/smithery.yaml +16 -0
  66. package/src/index.ts +13 -0
  67. package/src/interfaces/moon.ts +44 -0
  68. package/src/interfaces/noaa.ts +130 -0
  69. package/src/interfaces/parameters.ts +20 -0
  70. package/src/interfaces/sun.ts +57 -0
  71. package/src/schemas/common.ts +23 -0
  72. package/src/schemas/dpapi.ts +99 -0
  73. package/src/server/config.ts +43 -0
  74. package/src/server/mcp-server.ts +135 -0
  75. package/src/services/dpapi-service.ts +187 -0
  76. package/src/services/moon-phase-service.ts +167 -0
  77. package/src/services/noaa-parameters-service.ts +139 -0
  78. package/src/services/noaa-service.ts +171 -0
  79. package/src/services/sun-service.ts +275 -0
  80. package/src/tools/derived-product-tools.ts +180 -0
  81. package/src/tools/index.ts +40 -0
  82. package/src/tools/moon-tools.ts +79 -0
  83. package/src/tools/parameter-tools.ts +82 -0
  84. package/src/tools/station-tools.ts +57 -0
  85. package/src/tools/sun-tools.ts +120 -0
  86. package/src/tools/water-tools.ts +166 -0
  87. package/src/types/moon.ts +27 -0
  88. package/src/types/sun.ts +51 -0
  89. package/src/types/suncalc.d.ts +110 -0
  90. package/test-dpapi.js +0 -0
  91. 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
+ }