noaa-tidesandcurrents-mcp

by RyanCardin15
Verified
  • src
import { FastMCP } from 'fastmcp'; import dotenv from 'dotenv'; import { z } from 'zod'; import { NoaaService } from './noaa-service.js'; // Load environment variables dotenv.config(); // Common parameter schemas const StationSchema = z.string().min(1).describe('Station ID'); const DateSchema = z.string().optional().describe('Date to retrieve data for ("today", "latest", "recent", or specific date)'); const BeginDateSchema = z.string().optional().describe('Start date (YYYYMMDD or MM/DD/YYYY)'); const EndDateSchema = z.string().optional().describe('End date (YYYYMMDD or MM/DD/YYYY)'); const RangeSchema = z.number().optional().describe('Number of hours to retrieve data for'); const DatumSchema = z.string().optional().describe('Datum to use (MLLW, MSL, etc.)'); const UnitsSchema = z.enum(['english', 'metric']).optional().describe('Units to use ("english" or "metric")'); const TimeZoneSchema = z.enum(['gmt', 'lst', 'lst_ldt']).optional().describe('Time zone (gmt, lst, lst_ldt)'); const FormatSchema = z.enum(['json', 'xml', 'csv']).optional().describe('Output format (json, xml, csv)'); const BinSchema = z.number().optional().describe('Bin number'); const IntervalSchema = z.string().optional().describe('Interval (hilo, hl, h, or a number for minutes)'); // Create NOAA service const noaaService = new NoaaService({ applicationName: process.env.APPLICATION_NAME || 'NOAA_MCP_Server' }); // Create the MCP server const server = new FastMCP({ name: 'NOAA Tides and Currents MCP Server', version: '1.0.0' }); // Add water levels tool server.addTool({ name: 'get_water_levels', description: 'Get water level data for a station', parameters: z.object({ station: StationSchema, date: DateSchema, begin_date: BeginDateSchema, end_date: EndDateSchema, range: RangeSchema, datum: DatumSchema, units: UnitsSchema, time_zone: TimeZoneSchema, format: FormatSchema, }).refine( data => (data.date || (data.begin_date && data.end_date) || (data.begin_date && data.range) || (data.end_date && data.range) || data.range), { message: "You must provide either 'date', 'begin_date' and 'end_date', 'begin_date' and 'range', 'end_date' and 'range', or just 'range'" } ), execute: async (params) => { try { const result = await noaaService.getWaterLevels(params); return JSON.stringify(result); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to get water levels: ${error.message}`); } throw new Error('Failed to get water levels'); } } }); // Add tide predictions tool server.addTool({ name: 'get_tide_predictions', description: 'Get tide prediction data', parameters: z.object({ station: StationSchema, begin_date: BeginDateSchema, end_date: EndDateSchema, date: DateSchema, range: RangeSchema, datum: DatumSchema, units: UnitsSchema, time_zone: TimeZoneSchema, interval: IntervalSchema, format: FormatSchema, }).refine( data => (data.date || (data.begin_date && data.end_date) || (data.begin_date && data.range) || (data.end_date && data.range) || data.range), { message: "You must provide either 'date', 'begin_date' and 'end_date', 'begin_date' and 'range', 'end_date' and 'range', or just 'range'" } ), execute: async (params) => { try { const result = await noaaService.getTidePredictions(params); return JSON.stringify(result); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to get tide predictions: ${error.message}`); } throw new Error('Failed to get tide predictions'); } } }); // Add currents tool server.addTool({ name: 'get_currents', description: 'Get currents data for a station', parameters: z.object({ station: StationSchema, date: DateSchema, begin_date: BeginDateSchema, end_date: EndDateSchema, range: RangeSchema, bin: BinSchema, units: UnitsSchema, time_zone: TimeZoneSchema, format: FormatSchema, }).refine( data => (data.date || (data.begin_date && data.end_date) || (data.begin_date && data.range) || (data.end_date && data.range) || data.range), { message: "You must provide either 'date', 'begin_date' and 'end_date', 'begin_date' and 'range', 'end_date' and 'range', or just 'range'" } ), execute: async (params) => { try { const result = await noaaService.getCurrents(params); return JSON.stringify(result); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to get currents: ${error.message}`); } throw new Error('Failed to get currents'); } } }); // Add current predictions tool server.addTool({ name: 'get_current_predictions', description: 'Get current predictions', parameters: z.object({ station: StationSchema, date: DateSchema, begin_date: BeginDateSchema, end_date: EndDateSchema, range: RangeSchema, bin: BinSchema, interval: z.string().optional().describe('Interval (MAX_SLACK or a number for minutes)'), vel_type: z.enum(['speed_dir', 'default']).optional().describe('Velocity type (speed_dir or default)'), units: UnitsSchema, time_zone: TimeZoneSchema, format: FormatSchema, }).refine( data => (data.date || (data.begin_date && data.end_date) || (data.begin_date && data.range) || (data.end_date && data.range) || data.range), { message: "You must provide either 'date', 'begin_date' and 'end_date', 'begin_date' and 'range', 'end_date' and 'range', or just 'range'" } ), execute: async (params) => { try { const result = await noaaService.getCurrentPredictions(params); return JSON.stringify(result); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to get current predictions: ${error.message}`); } throw new Error('Failed to get current predictions'); } } }); // Add meteorological data tool server.addTool({ name: 'get_meteorological_data', description: 'Get meteorological data', parameters: z.object({ station: StationSchema, product: z.string().min(1).describe('Product (air_temperature, wind, etc.)'), date: DateSchema, begin_date: BeginDateSchema, end_date: EndDateSchema, range: RangeSchema, units: UnitsSchema, time_zone: TimeZoneSchema, format: FormatSchema, }).refine( data => (data.date || (data.begin_date && data.end_date) || (data.begin_date && data.range) || (data.end_date && data.range) || data.range), { message: "You must provide either 'date', 'begin_date' and 'end_date', 'begin_date' and 'range', 'end_date' and 'range', or just 'range'" } ), execute: async (params) => { try { const result = await noaaService.getMeteorologicalData(params); return JSON.stringify(result); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to get meteorological data: ${error.message}`); } throw new Error('Failed to get meteorological data'); } } }); // Add stations tool server.addTool({ name: 'get_stations', description: 'Get list of stations', parameters: z.object({ type: z.string().optional().describe('Station type (waterlevels, currents, etc.)'), units: UnitsSchema, format: z.enum(['json', 'xml']).optional().describe('Output format (json, xml)'), }), execute: async (params) => { try { const result = await noaaService.getStations(params); return JSON.stringify(result); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to get stations: ${error.message}`); } throw new Error('Failed to get stations'); } } }); // Add station details tool server.addTool({ name: 'get_station_details', description: 'Get detailed information about a station', parameters: z.object({ station: StationSchema, units: UnitsSchema, format: z.enum(['json', 'xml']).optional().describe('Output format (json, xml)'), }), execute: async (params) => { try { const result = await noaaService.getStationDetails(params); return JSON.stringify(result); } catch (error) { if (error instanceof Error) { throw new Error(`Failed to get station details: ${error.message}`); } throw new Error('Failed to get station details'); } } }); // Start the server const transportType = process.env.TRANSPORT_TYPE || 'stdio'; const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3000; if (transportType === 'stdio') { server.start({ transportType: 'stdio' }); } else if (transportType === 'sse') { server.start({ transportType: 'sse', sse: { endpoint: '/mcp', port: PORT } }); }